本人之后又基于 Flutter SDK 1.12.13+hotfix.8 版本,做过一次动态更新。
Flutter 动态下发更新(Android 端)
热修复,目前只支持 Android 端的热修复。
纯Flutter代码,Google已经是支持热修复了,flutter.jar 里面也有相关的逻辑代码。利用的是微软的 CodePush。
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 等相关文件就可以了。