在上一篇 文章中,简单分析了一下 Flutter 在 Android 端的启动流程,虽然没有更深刻的分析,可是咱们能够了解到,对于 Flutter 端的 Dart VM 的启动等,是经过 Android 传递的资源(或者说路径)过去,Dart VM 加载这些资源完成初始化的,那么咱们能够经过动态替换资源就能够达到热更新的目的。git
注意:github
本次测试的开发环境:shell
经过以前文章的分析,能够知道,FlutterMain 这个类中,会传递指定资源路径,提供给 Dart VM 进行初始化。编程
为了可以让 Dart VM 加载咱们修改以后的 so 库,咱们确定须要将修改后的 so 库放到 app 的私有目录下。这里直接从手机根目录下获取,固然从网络下载等都是一样的道理。 先定义一个辅助类,将文件复制到手机私有目录下。bash
public class FlutterFileUtils {
///将文件拷贝到私有目录
public static String copyLibAndWrite(Context context, String fileName){
try {
File dir = context.getDir("libs", Activity.MODE_PRIVATE);
File destFile = new File(dir.getAbsolutePath() + File.separator + fileName);
if (destFile.exists() ) {
destFile.delete();
}
if (!destFile.exists()){
boolean res = destFile.createNewFile();
if (res){
String path = Environment.getExternalStorageDirectory().toString();
FileInputStream is = new FileInputStream(new File(path + "/" + fileName));
FileOutputStream fos = new FileOutputStream(destFile);
byte[] buffer = new byte[is.available()];
int byteCount;
while ((byteCount = is.read(buffer)) != -1){
fos.write(buffer,0,byteCount);
}
fos.flush();
is.close();
fos.close();
return destFile.getAbsolutePath();
}
}
}catch (IOException e){
e.printStackTrace();
}
return "";
}
}
复制代码
在程序启动的时候,咱们调用这个方法,将文件复制过去,也就是在 MainActivity 的 onCreate 方法中。微信
@Override
protected void onCreate(Bundle savedInstanceState) {
String path = FlutterFileUtils.copyLibAndWrite(MainActivity.this,"libapp_fix.so");
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
复制代码
复制文件等操做都须要读写权限,不要忘了。网络
在以前分析启动流程的文章中,提到过,MainActivity 继承自 FlutterActivity,而 FlutterActivity 只是一个代理类,真正的操做都是在 FlutterActivityDelegate 这个类中进行的,而在 FlutterActivityDelegate 中会调用 FlutterMain 中的方法进行 Dart VM 等的初始化。 所以咱们要作的就是,修改 FlutterActivity 和 FlutterActivityDelegate 这两个类,以达到修改 FlutterMain 的目的。这里为了方便,只是简单的复制了一份代码,将 FlutterActivity 改成 HotFixFlutterActivity,FlutterActivityDelegate 改成 HotFixFlutterActivityDelegate ,而后修改里面的代码,固然还有其余的方法,这里不在演示。app
public class MainActivity extends HotFixFlutterActivity implements EasyPermissions.PermissionCallbacks
复制代码
public class HotFixFlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, HotFixFlutterActivityDelegate.ViewFactory {
private final HotFixFlutterActivityDelegate delegate = new HotFixFlutterActivityDelegate(this, this);
private final FlutterActivityEvents eventDelegate;
private final FlutterView.Provider viewProvider;
private final PluginRegistry pluginRegistry;
public HotFixFlutterActivity() {
this.eventDelegate = this.delegate;
this.viewProvider = this.delegate;
this.pluginRegistry = this.delegate;
}
...
}
复制代码
代码修改到这里,当程序运行后,MainActivity 的 onCreate 方法里面会执行到 HotFixFlutterActivityDelegate 的 onCreate 方法中,而在这里,会调用 FlutterMain 里面的方法进行初始化操做,所以咱们还须要修改 onCreate 这个方法。框架
onCreate 中默认调用的代码以下:ide
FlutterMain.ensureInitializationComplete(this.activity.getApplicationContext(), args);
复制代码
咱们确定须要本身定义一个相似的文件,修改里面的方法,来提供咱们调用达到替换资源的目的。好比咱们定义的相似的类叫 MyFlutterMain,那么 这里的代码修改成以下:
public void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= 21) {
Window window = this.activity.getWindow();
window.addFlags(-2147483648);
window.setStatusBarColor(1073741824);
window.getDecorView().setSystemUiVisibility(1280);
}
String[] args = getArgsFromIntent(this.activity.getIntent());
MyFlutterMain.startInitialization(this.activity.getApplicationContext());
MyFlutterMain.ensureInitializationComplete(this.activity.getApplicationContext(), args);
this.flutterView = this.viewFactory.createFlutterView(this.activity);
if (this.flutterView == null) {
FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView();
this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView);
this.flutterView.setLayoutParams(matchParent);
this.activity.setContentView(this.flutterView);
this.launchView = this.createLaunchView();
if (this.launchView != null) {
this.addLaunchView();
}
}
if (!this.loadIntent(this.activity.getIntent())) {
String appBundlePath = MyFlutterMain.findAppBundlePath();
if (appBundlePath != null) {
this.runBundle(appBundlePath);
}
}
}
复制代码
注意,这里多了一行:
MyFlutterMain.startInitialization(this.activity.getApplicationContext());
复制代码
主要是在ensureInitializationComplete这里,会进行一个判断:
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
} else if (sSettings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
}
复制代码
而只有在 startInitialization 以后,sSettings 才会被初始化,正常状况下,FlutterMain.startInitialization 这个方法是在 Application 的 onCreate 中调用的:
public class FlutterApplication extends Application {
private Activity mCurrentActivity = null;
public FlutterApplication() {
}
@CallSuper
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
public Activity getCurrentActivity() {
return this.mCurrentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity) {
this.mCurrentActivity = mCurrentActivity;
}
}
复制代码
由于咱们没有修改这里的代码,因此咱们要本身初始化一下,固然也能够本身在定义一个 Application 而后修改这里的代码。
这里主要是修改 MyFlutterMain 中的 ensureInitializationComplete 方法,加载咱们本身复制到手机私用目录下的那个 so 就好了。
public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
if (!isRunningInRobolectricTest) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
} else if (sSettings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
} else if (!sInitialized) {
try {
if (sResourceExtractor != null) {
sResourceExtractor.waitForCompletion();
}
List<String> shellArgs = new ArrayList();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + "libflutter.so");
if (args != null) {
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
shellArgs.add("--aot-shared-library-name=" + sAotSharedLibraryName);
File dir = applicationContext.getDir("libs", Activity.MODE_PRIVATE);
String libPath = dir.getAbsolutePath() + File.separator + "libapp_fix.so";
shellArgs.add("--aot-shared-library-name=" + libPath);
shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
if (sSettings.getLogTag() != null) {
shellArgs.add("--log-tag=" + sSettings.getLogTag());
}
String appStoragePath = PathUtils.getFilesDir(applicationContext);
String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
FlutterJNI.nativeInit(applicationContext, (String[])shellArgs.toArray(new String[0]), (String)kernelPath, appStoragePath, engineCachesPath);
sInitialized = true;
} catch (Exception var7) {
throw new RuntimeException(var7);
}
}
}
}
复制代码
这里的路径和名称须要对应上,我已将修复后的 so 重命名为 libapp_fix.so ,并经过
shellArgs.add("--aot-shared-library-name=" + sAotSharedLibraryName);
复制代码
这行代码传递给底层。 同时,so 库路径经过以下代码传递:
File dir = applicationContext.getDir("libs", Activity.MODE_PRIVATE);
String libPath = dir.getAbsolutePath() + File.separator + "libapp_fix.so";
shellArgs.add("--aot-shared-library-name=" + libPath);
复制代码
至此,咱们修改了代码,让程序初始化的时候,加载咱们修改过的资源文件了。
修复步骤:
因为上面的代码已经修改成加载私有目录下的 libapp_fix.so ,若是 app 直接运行确定是不行的,所以咱们须要先打一个 release 包,解压拿到里面的 libapp.so ,并修改成 libapp_fix.so,而后放到手机根目录下,这样程序启动后,会把这个文件复制到私有目录。
这里注意一下,打 release 包须要配置一下签名文件 。
代码就是初始化项目的代码,修改成点击按钮,数字加2 :
效果以下:
修改代码以下 :
一样,解压 apk,重命名 libapp.so 为 libapp_fix.so,放到手机根目录下。
先杀掉进程,重启应用,查看效果:
能够看到,已经完成了修复。
欢迎关注「Flutter 编程开发」微信公众号 。