Flutter 接安卓热修复的坑
主项目最近上了flutter,主项目编译正常,之后打patch准备tinker热修复灰度上线测试,结果运行到相关代码崩溃:
[ERROR:flutter/runtime/dart_vm_data.cc(19)] VM snapshot invalid and could not be inferred from settings.
[ERROR:flutter/runtime/dart_vm.cc(238)] Could not setup VM data to bootstrap the VM from.
[ERROR:flutter/runtime/dart_vm_lifecycle.cc(89)] Could not create Dart VM instance.
[FATAL:flutter/shell/common/shell.cc(218)] Check failed: vm. Must be able to initialize the VM
跟了下代码
flutter构造FlutterView之前会确认是否初始化,FlutterMain.ensureInitializationComplete方法。然后里面会把so文件的地址给到shellArgs里传入FlutterJNI。然而这里的地址并不是patch包里的地址,从而导致出错。
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);
shellArgs.add("--aot-shared-library-name=" + applicationInfo.nativeLibraryDir + File.separator + sAotSharedLibraryName);
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) {
Log.e("FlutterMain", "Flutter initialization failed.", var7);
throw new RuntimeException(var7);
}
}
}
}
这边我们可以通过修改flutter生成的代码或者使用hook等方式替换 List<String> shellArgs的add方法,把so文件的路径替换为tinker里寻找so文件的路径即可,其他热修复平台可以参考类似策略
fun add(list: MutableList<Any>, s: Any): Boolean {
val string = s.toString()
val match = """(--[a-z-]+=).*/(lib\w*\.so)""".toRegex().find(string) ?: return list.add(s)
val prefix = match.groupValues[1]
val libName = match.groupValues[2]
return list.add(prefix + findLibraryPath(appContext, "lib/armeabi-v7a", libName))
}
fun findLibraryPath(context: Context, relativePath: String, libName: String): String {
val patchedPath = findPatchedLibraryPath(context, relativePath, libName)
if (patchedPath != null) {
return patchedPath
}
val applicationInfo = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
return applicationInfo.nativeLibraryDir + File.separator + getFullLibName(libName)
}
fun findPatchedLibraryPath(context: Context, relativePath: String, libName: String): String? {
val tinker = Tinker.with(context)
val relativeLibPath = "$relativePath/${getFullLibName(libName)}"
if (tinker.isEnabledForNativeLib && tinker.isTinkerLoaded) {
val loadResult = tinker.tinkerLoadResultIfPresent
if (loadResult.libs == null) {
return null
}
for (name in loadResult.libs.keys) {
if (name != relativeLibPath) {
continue
}
val patchLibraryPath = loadResult.libraryDirectory.toString() + "/" + name
val library = File(patchLibraryPath)
if (!library.exists()) {
continue
}
//whether we check md5 when load
val verifyMd5 = tinker.isTinkerLoadVerify
if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadResult.libs[name])) {
tinker.loadReporter.onLoadFileMd5Mismatch(library, ShareConstants.TYPE_LIBRARY)
} else {
return patchLibraryPath
}
}
}
return null
}
private fun getFullLibName(libName: String): String {
var fullLibName = if (libName.startsWith("lib")) libName else "lib$libName"
fullLibName = if (fullLibName.endsWith(".so")) fullLibName else "$fullLibName.so"
return fullLibName
}
总结,flutter模块更新会生成货修改对应的so文件,并且在创建flutterview的时候会通过类似命令行的形式传入so文件的地址,在热修复的case下需要手动替换so路径为对应patch里的so才可以正常work