Codeegg’s 800th tweet

Author: Big brother QZW

Blog: https://juejin.cn/post/6844903992066048013

Size girls see the world

1. Introduction

Flutter officially announced on GitHub that it does not support hot updates for the time being, but there is some hot update code embedded in the Source code of Flutter, and it is possible to implement dynamic updates on Android by some means of our own.

2. Study on Flutter products

Whether it’s creating a complete Flutter project, or Native integrating Flutter with a Moudle, or Native integrating Flutter with an AAR, Finally, all the APPS of Flutter on the Andorid end exist as the UI products of Native project + Flutter. So disassemble a Flutter here and compile it in release mode to generate an AAR package for analysis:

We focus on assets, JNI and Libs. Other files are products of Nactive shell project.

jniThis file provides a C++ implementation of the Flutter Engine layer, including skia, Dart, Text, and other support for Flutter.

libsJar. This file is the Java implementation of the Flutter embedding layer, which provides support for many of the flutter Native layer’s system functions, such as creating threads.

assets: This directory is divided into two parts:

1. Flutter_assets directory: this directory stores Flutter resources of our application layer, including images, fonts, etc.

2. Isolate_snapshot_data, isolate_snapshot_instr, vm_snapshot_data, vm_snapshot_instr files: These four files correspond to the data segment of the ISOLATE, VM, and command segment files respectively. These four files are the product of our own Flutter code.

3. The heat of the Flutter code

To explore the

In our Native project, we create a View by calling the Flutter class in the FlutterMainActivity:

flutterView = Flutter.createView(this, getLifecycle(), route);layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams         .MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT);addContentView(flutterView, layoutParams);Copy the code

A review of the Flutter code shows that the Flutter class does several things: 1. Use FlutterNative to load the View, set the route, use Lifecycle binding lifecycle; 2. Use FlutterMain to initialize, focusing here.

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

So, the relevant code to actually initialize is in FlutterMian:

public static void startInitialization(Context applicationContext,                               FlutterMain.Settings settings) {if (Looper.myLooper() ! = Looper.getMainLooper()) {  throw new IllegalStateException("startInitialization must be called on the main thread");} else if (sSettings == null) {  sSettings = settings;  long initStartTimestampMillis = SystemClock.uptimeMillis();  initConfig(applicationContext);  initAot(applicationContext);  initResources(applicationContext);  System.loadLibrary("flutter");  long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;  nativeRecordStartTimestamp(initTimeMillis); }}Copy the code

In startInitialization, three initialization methods initConfig (applicationContext), initAot (applicationContext), and InitResources (applicationContext), which finally records the execution time;

In initConfig:

private static void initConfig(Context applicationContext) {try { Bundle metadata = applicationContext.getPackageManager().       getApplicationInfo(applicationContext           .getPackageName(), 128).metaData;if (metadata ! = null) {  sAotSharedLibraryPath = metadata.getString(PUBLIC_AOT_AOT_SHARED_LIBRARY_PATH, "app.so");  sAotVmSnapshotData = metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_DATA_KEY, "vm_snapshot_data");  sAotVmSnapshotInstr = metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_INSTR_KEY, "vm_snapshot_instr");  sAotIsolateSnapshotData = metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_DATA_KEY, "isolate_snapshot_data");  sAotIsolateSnapshotInstr = metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_INSTR_KEY, "isolate_snapshot_instr");  sFlx = metadata.getString(PUBLIC_FLX_KEY, "app.flx");  sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, "flutter_assets"); }} catch (NameNotFoundException var2) {  throw new RuntimeException(var2); }}Copy the code

In initResources:

sResourceExtractor = new ResourceExtractor(applicationContext);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();Copy the code

In the ResourceExtractor class, you know by name that this class does resource extraction. Fetch the add related Flutter files from assets. The ExtractTask doInBackground method in this class:

File dataDir = new File(PathUtils.getDataDirectory(                      ResourceExtractor.this.mContext));Copy the code

Data /data/ package name /app_flutter specifies the destination of the resource extraction, as follows:

As can be seen from the figure, the access permissions of this folder are both read and write. Therefore, in theory, we can download our Flutter products and copy them from memory to this directory to implement dynamic code updates.

Code implementation

public class FlutterUtils { private static String TAG = "FlutterUtils.class"; private static String flutterZipName = "flutter-code.zip"; private static String fileSuffix = ".zip"; private static String zipPath = Environment.getExternalStorageDirectory()              .getPath() + "/k12/" + flutterZipName; private static String targetDirPath = zipPath.replace(fileSuffix, ""); private static String targetDirDataPath = zipPath.replace(fileSuffix, "/data"); / * *Step 1: Decompress the zip file of the Flutter code* /public static void unZipFlutterFile() {  Log.i(TAG, "unZipFile: Start");  try { unZipFile(zipPath, targetDirPath);    Log.i(TAG, "unZipFile: Finish");  } catch (Exception e) {    e.printStackTrace();  } } / * *Step 2: Move the relevant Flutter files to the relevant directory of AppData, which will be invoked when APP starts* * @param mContext needs to get the AppData directory* /public static void copyDataToFlutterAssets(Context mContext) {   String appDataDirPath = PathUtils.getDataDirectory(mContext.getApplicationContext()) + File.separator;   Log.d(TAG, "copyDataToFlutterAssets-filesDirPath:" + targetDirDataPath);   Log.d(TAG, "copyDataToFlutterAssets-appDataDirPath:" + appDataDirPath);   File appDataDirFile = new File(appDataDirPath);   File filesDirFile = new File(targetDirDataPath);   File[] files = filesDirFile.listFiles();   for (File srcFile : files) {     if (srcFile.getPath().contains("isolate_snapshot_data") || srcFile.getPath().contains("isolate_snapshot_instr") || srcFile.getPath().contains("vm_snapshot_data") || srcFile.getPath().contains("vm_snapshot_instr")) {     File targetFile = new File(appDataDirFile + "/" + srcFile.getName());    FileUtil.copyFileByFileChannels(srcFile, targetFile);     Log.i(TAG, "copyDataToFlutterAssets-copyFile:" + srcFile.getPath());   }   } Log.i(TAG, "copyDataToFlutterAssets: Finish");   / * ** Unzip the file to the specified directory ** @param zipFileString Zip file path* @param outPathString Target path  * @throws Exception * /  private static void unZipFile(String zipFileString, String outPathString) {    try {    ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString));    ZipEntry zipEntry; String szName = ""; while ((zipEntry = inZip.getNextEntry()) ! = null) { szName = zipEntry.getName();    if (zipEntry.isDirectory()) {       szName = szName.substring(0, szName.length() - 1);       File folder = new File(outPathString + File.separator + szName);       folder.mkdirs();     } else {       File file = new File(outPathString + File.separator + szName); if (! file.exists()) {      Log.d(TAG, "Create the file:" + outPathString + File.separator + szName);       file.getParentFile().mkdirs(); file.createNewFile();   }     FileOutputStream out = new FileOutputStream(file);   int len;   byte[] buffer = new byte[1024];   while ((len = inZip.read(buffer)) != -1) {     out.write(buffer, 0, len); out.flush();   }     out.close(); }}    inZip.close();   } catch (Exception e) {     Log.i(TAG,e.getMessage());     e.printStackTrace();     }   }   / * ** Copy files using FileChannels.* * @param source Specifies the original path* @param dest Destination path* /  public static void copyFileByFileChannels(File source, File dest) {     FileChannel inputChannel = null;     FileChannel outputChannel = null;     try {       inputChannel = new FileInputStream(source).getChannel();       outputChannel = new FileOutputStream(dest).getChannel();       outputChannel.transferFrom(inputChannel, 0, inputChannel.size());       refreshMedia(BaseApplication.getBaseApplication(), dest);     } catch (Exception e) {       e.printStackTrace();     } finally {      try {         inputChannel.close();         outputChannel.close();     } catch (IOException e) {       e.printStackTrace();     }     }/ * ** Update the media library  * * @param cxt   * @param files */   public static void refreshMedia(Context cxt, File... files) {     for (File file : files) {     String filePath = file.getAbsolutePath();     refreshMedia(cxt, filePath);     }   }     public static void refreshMedia(Context cxt, String... filePaths) {     MediaScannerConnection.scanFile(cxt.getApplicationContext(), filePaths, null, null);   }}Copy the code
Copy the code

4. Hot update of Flutter resources

After our App is installed on the phone, it is difficult to modify the resources under Assets. Therefore, the current solution to replace the resources is to use Flutter API: image.file () to read the images from the memory card.

Usually, our Flutter project should contain images of the App. Try to ensure that existing images are used for hot updates.

Second, we can use Image.net Work () to load images from web resources,

If not, the solution to Flutter is to use image.file (), place the resource Image into a Zip directory and load the Flutter code using image.file ().

  1. Get the memory address dataDir of the picture folder through Native layer method;

  2. Judge whether the picture exists, there is load, there is no load existing picture placeholder;

new File(dataDir + 'hotupdate_test.png').existsSync()   ? Image.file(new File(dataDir + 'hotupdate_test.png'))   : Image.asset("images/net_error.png"),Copy the code

5. To summarize

In the Flutter code product replacement, the optimization space is limited because the four files replaced are all engine code directly loaded into memory. However, in the hot update of resources, resources are taken from Assets, so there should be a better solution.

The hot update to Flutter means that it can embed pages as endlessly as the H5 in one entry to the App, but with a smooth experience comparable to native.

In the future, if Flutter hot update technology is mature, application development may only need Android and IOS to realize the encapsulation of local business function modules, and the business and UI codes are stored in Flutter.

It can truly realize a piece of business code at both ends of the mobile device, and give the product the ability to dynamically deploy APP content without affecting user experience.

Related articles:

  • APP location is too frequent, I use reflection + dynamic agent to find the culprit

  • Boss says: App startup must speed up 35%!

  • One line of code will help you check the Android emulator

Question of the day:

There are more and more options for the thermal update of Flutter.

Exclusive upgrade community: I Finally Figured it out