In mobile development, we generally need to have thermal repair capability for our projects. However, Thermal repair is not officially supported by Flutter so far. This article will explore how this capability can be implemented. The Flutter version is based on 1.22.5. To achieve thermal repair requires an understanding of the Flutter original project and packaging products. Flutter packaging will punch our DART code into the so file libapp.so, so dynamically replacing the so file when we start up can achieve a hot fix. Flutter will configure a default project IO. Flutter. App. FlutterApplication as our application, only do one thing in this class, load the flutter related to the environment

public class FlutterApplication extends Application { @Override @CallSuper public void onCreate() { super.onCreate(); // Load the flutter environment flutterInjector.instance ().flutterLoader().startInitialization(this); } private Activity mCurrentActivity = null; public Activity getCurrentActivity() { return mCurrentActivity; } public void setCurrentActivity(Activity mCurrentActivity) { this.mCurrentActivity = mCurrentActivity; }}Copy the code

So the class we need to focus on is FlutterInjector

public final class FlutterInjector { private static FlutterInjector instance; public static FlutterInjector instance() { if (instance == null) { 1. 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. By default, a FlutterLoader instance fillDefaults() is injected; 3. Build FlutterInjector instance flutterLoader return FlutterInjector(shouldLoadNative, flutterLoader); }}}Copy the code

FlutterInjector class FlutterInjector class FlutterLoader class FlutterInjector class FlutterLoader class FlutterInjector class FlutterLoader class FlutterInjector class FlutterLoader class FlutterInjector class FlutterLoader is the main part of this process. Its function is to load relevant resources. If flutrterLoader needs to be obtained anywhere, it can be obtained using FlutterLoader () method of FlutterInjector.

/** 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; < -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - key stitching start 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); Key stitching end -- -- -- -- -- -- -- -- -- -- -- -- -- > 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; }}Copy the code

The key method of flutterLoader ensureInitializationComplete splicing shell commands to dartVM start our program, The key content is — aot-shared-library-name=libapp.so – aot – Shared library – name = nativeLibraryDir/libapp. So, I have then we only need to patch so we issued by the file path to replace FlutterApplicationInfo. AotSharedLibraryNam The value of the e field, then flutter starts to execute the content we deliver. Since FlutterApplicationInfo in FlutterLoader is private and aotSharedLibraryName in FlutterApplicationInfo is final, So we can’t through conventional means to modify FlutterApplicationInfo. AotSharedLibraryName value, only by reflection to modify, so we now we realize the train of thought is clear:

  1. Implement a FlutterLoader subclasses override ensureInitializationComplete method, modified 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. Get flutterApplicationInfo Field Field flutterApplicationInfoField = FlutterLoader.class.getDeclaredField("flutterApplicationInfo"); flutterApplicationInfoField.setAccessible(true); FlutterApplicationInfo flutterApplicationInfo = (FlutterApplicationInfo) flutterApplicationInfoField.get(this); / / 2. Get aotSharedLibraryName and modify the so for us path 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); }}Copy the code
  1. Define an Application class that injects its own implementation of FlutterLoader into 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); }}Copy the code

Now that we have implemented the hotfix core, to verify that we can test fix libapp_fix.so into our assets directory, In the oncreate method in the application of the file is copied to the applicationContext. GetFilesDir ()/libapp_fix. So to verify whether it can work.

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); }}Copy the code