在移动端开发我们一般非常需要我们的项目拥有热修复能力,而在flutter目前为止官方仍旧未支持热修复,本篇文章就探索下热修复这个功能如何实现。flutter版本基于1.22.5。要实现热修复需要对flutter原始项目和打包产物需要了解一下。flutter打包后会将我们的dart代码打进到libapp.so这个so文件,所以在我们启动的时候动态替换这个so文件就可以达到热修复。
flutter默认工程会配置一个io.flutter.app.FlutterApplication作为我们的application,这个类中只做了一件事,加载flutter相关的环境
public class FlutterApplication extends Application {
@Override
@CallSuper
public void onCreate() {
super.onCreate();
//加载flutter相关环境
FlutterInjector.instance().flutterLoader().startInitialization(this);
}
private Activity mCurrentActivity = null;
public Activity getCurrentActivity() {
return mCurrentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity) {
this.mCurrentActivity = mCurrentActivity;
}
}
所以我们需要关注的就是FlutterInjector这个类了
public final class FlutterInjector {
private static FlutterInjector instance;
public static FlutterInjector instance() {
if (instance == null) {
1.这里通过Builder构建则去构建实例
instance = new Builder().build();
}
return instance;
}
@NonNull
public FlutterLoader flutterLoader() {
return flutterLoader;
}
public static final class Builder {
private FlutterLoader flutterLoader;
public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) {
this.flutterLoader = flutterLoader;
return this;
}
private void fillDefaults() {
if (flutterLoader == null) {
flutterLoader = new FlutterLoader();
}
}
public FlutterInjector build() {
2.默认注入一个FlutterLoader实例
fillDefaults();
3.构建FlutterInjector实例,FlutterInjector持有flutterLoader
return new FlutterInjector(shouldLoadNative, flutterLoader);
}
}
}
这个类的通过构建者模式将会构建出FlutterInjector这个类,而这个类关键就是去持有FlutterLoader这个类的实例,通过命名我们就很容易发现FlutterInjector的作用以单例注入到其他类中,而FlutterLoader就是重头戏,他的作用是加载相关资源,如果需要在任意地方获取flutrterLoader就可以使用FlutterInjector的flutterLoader()方法拿到。
/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
public class FlutterLoader {
private FlutterApplicationInfo flutterApplicationInfo;
public void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) {
try {
InitResult result = initResultFuture.get();
List<String> shellArgs = new ArrayList<>();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
shellArgs.add(
"--icu-native-lib-path="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ DEFAULT_LIBRARY);
if (args != null) {
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
<------------------关键拼接开始
shellArgs.add(
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
// Most devices can load the AOT shared library based on the library name
// with no directory path. Provide a fully qualified path to the library
// as a workaround for devices where that fails.
shellArgs.add(
"--"
+ AOT_SHARED_LIBRARY_NAME
+ "="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
关键拼接结束------------->
shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
if (!flutterApplicationInfo.clearTextPermitted) {
shellArgs.add("--disallow-insecure-connections");
}
if (flutterApplicationInfo.domainNetworkPolicy != null) {
shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
}
if (settings.getLogTag() != null) {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
if (FlutterInjector.instance().shouldLoadNative()) {
FlutterJNI.nativeInit(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
}
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
}
}
public final class FlutterApplicationInfo {
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
final String aotSharedLibraryName;
final String nativeLibraryDir;
public FlutterApplicationInfo(
String aotSharedLibraryName,
String vmSnapshotData,
String isolateSnapshotData,
String flutterAssetsDir,
String domainNetworkPolicy,
String nativeLibraryDir,
boolean clearTextPermitted) {
this.aotSharedLibraryName =
aotSharedLibraryName == null ? DEFAULT_AOT_SHARED_LIBRARY_NAME : aotSharedLibraryName;
this.nativeLibraryDir = nativeLibraryDir;
}
}
flutterLoader中的关键方法ensureInitializationComplete拼接shell命令给dartVM启动我们的程序,拼接好后的关键内容是 -- aot-shared-library-name=libapp.so --aot-shared-library-name=nativeLibraryDir/libapp.so,我已我们就仅需要将我们下发的补丁so文件路径替换成FlutterApplicationInfo.aotSharedLibraryName字段的值,那么flutter启动后就是执行我们下发的内容了。由于FlutterLoader中的FlutterApplicationInfo是私有的并且FlutterApplicationInfo中的aotSharedLibraryName是final修饰的,所以我们不可能通过常规手段去修改FlutterApplicationInfo.aotSharedLibraryName的值,只能通过反射去修改了,所以我们现在我们实现的思路很清晰了就:
- 实现一个FlutterLoader的子类重写ensureInitializationComplete方法,修改FlutterApplicationInfo.aotSharedLibraryName
public class HIFlutterLoader extends FlutterLoader {
private static HIFlutterLoader instance;
private static final String FIX_SO = "libapp_fix.so";
@NonNull
public static HIFlutterLoader getInstance() {
if (instance == null) {
instance = new HIFlutterLoader();
}
return instance;
}
@Override
public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
File filesDir = applicationContext.getFilesDir();
File soFile = new File(filesDir, FIX_SO);
if (soFile.exists()) {
try {
//1.拿到flutterApplicationInfo字段
Field flutterApplicationInfoField = FlutterLoader.class.getDeclaredField("flutterApplicationInfo");
flutterApplicationInfoField.setAccessible(true);
FlutterApplicationInfo flutterApplicationInfo = (FlutterApplicationInfo) flutterApplicationInfoField.get(this);
//2.拿到aotSharedLibraryName并修改为我们的so路径
Field aotSharedLibraryNameField = FlutterApplicationInfo.class.getDeclaredField("aotSharedLibraryName");
aotSharedLibraryNameField.setAccessible(true);
aotSharedLibraryNameField.set(flutterApplicationInfo, soFile.getAbsolutePath());
Log.i("HIFlutterLoader", "load so name:" + aotSharedLibraryNameField.get(flutterApplicationInfo));
} catch (Exception e) {
e.printStackTrace();
}
}
super.ensureInitializationComplete(applicationContext, args);
}
}
- 定义一个Application类,类中将自己实现的FlutterLoader注入到FlutterInjector中
public class MApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HIFlutterLoader flutterLoader = HIFlutterLoader.getInstance();
FlutterInjector flutterInjector = new FlutterInjector.Builder().setFlutterLoader(flutterLoader).build();
FlutterInjector.setInstance(flutterInjector);
flutterLoader.startInitialization(this);
}
}
现在我们已经完成了热修复核心的实现,为了验证我们可以通过将测试修复的libapp_fix.so放到我们的assets目录下,在application中的oncreate方法中将该文件复制到applicationContext.getFilesDir()/libapp_fix.so以验证是否可以工作。
public class MApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
File filesDir = getFilesDir();
File fix = new File(filesDir, "libapp_fix.so");
Log.i("MApplication", "libapp_fix.so path:" + fix.getAbsolutePath());
if (!fix.exists()) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = getResources().getAssets().open("libapp_fix.so");
outputStream = new FileOutputStream(fix);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buf)) > 0) {
outputStream.write(buf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
HIFlutterLoader flutterLoader = HIFlutterLoader.getInstance();
FlutterInjector flutterInjector = new FlutterInjector.Builder().setFlutterLoader(flutterLoader).build();
FlutterInjector.setInstance(flutterInjector);
flutterLoader.startInitialization(this);
}
}