Flutter混合开发,热修复(Android端)

本人之后又基于 Flutter SDK 1.12.13+hotfix.8 版本,做过一次动态更新。
Flutter 动态下发更新(Android 端)

这里针对 Flutter SDK 1.2.0 版本做的修改;

热修复,目前只支持 Android 端的热修复。
纯Flutter代码,Google已经是支持热修复了,flutter.jar 里面也有相关的逻辑代码。利用的是微软的 CodePush。

Flutter混合开发,热修复(Android端)_第1张图片

Flutter 页面显示到 Android 端,实际就是用的 FlutterView 填充到 Activity或者 Fragment上的。

public static FlutterView createView(@NonNull final Activity activity, @NonNull final Lifecycle lifecycle, final String initialRoute) {
    FlutterMain.startInitialization(activity.getApplicationContext());
    FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), null);
    final FlutterNativeView nativeView = new FlutterNativeView(activity);
    final FlutterView flutterView = new FlutterView(activity, null, nativeView);
    ......
    return flutterView;
  }

FlutterMain.startInitialization 主要做了初始化配置信息、初始化AOT编译和初始化资源,最后一部分则是加载Flutter的Native环境。跟热修复相关的主要是第三步,初始化资源 initResources()

public class FlutterMain {
   ......
   private static final String SHARED_ASSET_DIR = "flutter_shared";
   private static final String SHARED_ASSET_ICU_DATA = "icudtl.dat";
   private static String sAotVmSnapshotData = "vm_snapshot_data";
   private static String sAotVmSnapshotInstr = "vm_snapshot_instr";
   private static String sAotIsolateSnapshotData = "isolate_snapshot_data";
   private static String sAotIsolateSnapshotInstr = "isolate_snapshot_instr";
   private static String sFlutterAssetsDir = "flutter_assets";
   public static void startInitialization(Context applicationContext, FlutterMain.Settings settings) {
       ......
       // 初始化配置信息
       initConfig(applicationContext);
       // 初始化AOT编译
       initAot(applicationContext);
       // 初始化资源
       initResources(applicationContext);
       // 加载Flutter的Native环境
       System.loadLibrary("flutter");
       ......
   }
   private static void initResources(Context applicationContext) {
       ......
       sResourceExtractor = new ResourceExtractor(applicationContext);
       String icuAssetPath = "flutter_shared" + File.separator + "icudtl.dat";
       sResourceExtractor.addResource(icuAssetPath);
       sIcuDataPath = PathUtils.getDataDirectory(applicationContext) + File.separator + icuAssetPath;
       sResourceExtractor.addResource(fromFlutterAssets(sFlx)).addResource(fromFlutterAssets(sAotVmSnapshotData)).addResource(fromFlutterAssets(sAotVmSnapshotInstr)).addResource(fromFlutterAssets(sAotIsolateSnapshotData)).addResource(fromFlutterAssets(sAotIsolateSnapshotInstr)).addResource(fromFlutterAssets("kernel_blob.bin"));
       if (sIsPrecompiledAsSharedLibrary) {
           sResourceExtractor.addResource(sAotSharedLibraryPath);
       } else {
           sResourceExtractor.addResource(sAotVmSnapshotData).addResource(sAotVmSnapshotInstr).addResource(sAotIsolateSnapshotData).addResource(sAotIsolateSnapshotInstr);
       }
       sResourceExtractor.start();
   }
}
public class ResourceExtractor {
	......
	private final HashSet<String> mResources;
	ResourceExtractor addResource(String resource) {
		this.mResources.add(resource);
		return this;
	}
   private class ExtractTask extends AsyncTask<Void, Void, Void> {
        protected Void doInBackground(Void... unused) {
            // 拿到 data/data/app_flutter 目录
            File dataDir = new File(PathUtils.getDataDirectory(ResourceExtractor.this.mContext));
            File activeFile;
            try {
                // 是否有要更新补丁包
                ......
                // timestamp 判断是否需要重新拷贝到 dataDir 下
                String timestamp = ResourceExtractor.this.checkTimestamp(dataDir);
                if (timestamp == null) {
                    activeFile = null;
                    return activeFile;
                }
                // 删除 app_flutter目录相关文件,尝试执行解压补丁包,覆盖文件
                ResourceExtractor.this.deleteFiles();
                if (!ResourceExtractor.this.extractUpdate(dataDir)) {
                    activeFile = null;
                    return activeFile;
                }
                // 尝试从 apk assets 拷贝到 dataDir 下
                if (!ResourceExtractor.this.extractAPK(dataDir)) {
                    activeFile = null;
                    return activeFile;
                }
                // 创建 timestamp,下次就不用重新拷贝了
                (new File(dataDir, timestamp)).createNewFile();
                ......
                activeFile = null;
            } finally {
                ......
            }
            return activeFile;
        }
    }
    private boolean extractAPK(File dataDir) {
        AssetManager manager = this.mContext.getResources().getAssets();
        byte[] buffer = null;
        // 这里的 mResources 就是上面 FlutterMain.initResources() 里面添加的 文件|目录名
        Iterator var4 = this.mResources.iterator();
        while(var4.hasNext()) {
            String asset = (String)var4.next();
            try {
                File output = new File(dataDir, asset);
                if (!output.exists()) {
                    if (output.getParentFile() != null) {
                        output.getParentFile().mkdirs();
                    }
                    InputStream is = manager.open(asset);
                    // 文件拷贝逻辑
                    ......
                    OutputStream os = new FileOutputStream(output);
                    int count;
                    while((count = is.read(buffer, 0, 16384)) != -1) {
                        os.write(buffer, 0, count);
                    }
                    ......
                }
            } catch (FileNotFoundException var41) {
            } catch (IOException var42) {
                return false;
            }
        }
        return true;
    }
}

FlutterMain.ensureInitializationComplete,Java层只是把 vm,isolate 相关文件路径传到C层,初始化 Dart VM,主要代码也在 C 层了。

public class FlutterMain {
   ......
   private static void initAot(Context applicationContext) {
        Set<String> assets = listAssets(applicationContext, "");
        sIsPrecompiledAsBlobs = assets.containsAll(Arrays.asList(sAotVmSnapshotData, sAotVmSnapshotInstr, sAotIsolateSnapshotData, sAotIsolateSnapshotInstr));
        sIsPrecompiledAsSharedLibrary = assets.contains(sAotSharedLibraryPath);
        if (sIsPrecompiledAsBlobs && sIsPrecompiledAsSharedLibrary) {
            throw new RuntimeException("Found precompiled app as shared library and as Dart VM snapshots.");
        }
    }
    // flutter 本地资源路径(本地图片等等)
    public static String findAppBundlePath(Context applicationContext) {
        String dataDirectory = PathUtils.getDataDirectory(applicationContext);
        File appBundle = new File(dataDirectory, sFlutterAssetsDir);
        return appBundle.exists() ? appBundle.getPath() : null;
    }
    public static void ensureInitializationComplete(Context applicationContext, String[] args) {
    		......
    		try {
                sResourceExtractor.waitForCompletion();
                List<String> shellArgs = new ArrayList();
                shellArgs.add("--icu-data-file-path=" + sIcuDataPath);
                if (args != null) {
                    Collections.addAll(shellArgs, args);
                }
                if (sIsPrecompiledAsSharedLibrary) {
                    shellArgs.add("--aot-shared-library-path=" + new File(PathUtils.getDataDirectory(applicationContext), sAotSharedLibraryPath));
                } else {
                    if (sIsPrecompiledAsBlobs) {
                    	// release打包, sIsPrecompiledAsBlobs = true 的,这里传到C层的aot-path是 data/data/app_flutter目录
                        shellArgs.add("--aot-snapshot-path=" + PathUtils.getDataDirectory(applicationContext));
                    } else {
                        shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
                        shellArgs.add("--aot-snapshot-path=" + PathUtils.getDataDirectory(applicationContext) + "/" + sFlutterAssetsDir);
                    }
                    shellArgs.add("--vm-snapshot-data=" + sAotVmSnapshotData);
                    shellArgs.add("--vm-snapshot-instr=" + sAotVmSnapshotInstr);
                    shellArgs.add("--isolate-snapshot-data=" + sAotIsolateSnapshotData);
                    shellArgs.add("--isolate-snapshot-instr=" + sAotIsolateSnapshotInstr);
                }
                if (sSettings.getLogTag() != null) {
                    shellArgs.add("--log-tag=" + sSettings.getLogTag());
                }
                String appBundlePath = findAppBundlePath(applicationContext);
                String appStoragePath = PathUtils.getFilesDir(applicationContext);
                String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
                nativeInit(applicationContext, (String[])shellArgs.toArray(new String[0]), appBundlePath, appStoragePath, engineCachesPath);
                sInitialized = true;
            } catch (Exception var6) {
                Log.e("FlutterMain", "Flutter initialization failed.", var6);
                throw new RuntimeException(var6);
            }    
    }
}

综合上面就可以看出,Flutter 的纯 Dart 代码实际都是指定文件路径给C层,而代码的执行全部交由C层处理了。而从官方Java层代码可以看出这个路径自身项目是可以访问的,所以实现动态更新,只需要替换那几个 vm,isolate 等相关文件就可以了。

你可能感兴趣的:(Flutter)