Author: Big brother

preface

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.

Study of 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.

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

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: The 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.

Flutter code heat more

# # #

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

Looking at the code for the Flutter class, we find that the Flutter class does several things:

  1. Use FlutterNative to load the View, set the route, use Lifecycle binding lifecycle;
  2. Initialize using FlutterMain, 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"); /** * public static void decompress the file of a FlutterunZipFlutterFile() { 
 Log.i(TAG, "unZipFile: Start"); 
 try { unZipFile(zipPath, targetDirPath); 
 Log.i(TAG, "unZipFile: Finish"); } catch (Exception e) { e.printStackTrace(); }} /** * update the Flutter code */ public static void is required when the APP starts to call * * @param mContext to obtain the AppData directory 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 a specified directory ** @param zipFileString Zip file path * @param outPathString Target path * @throws Exception */ private static void unZipFile(String zipFileString, String outPathString) { try { ZipInputStreaminZip = 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. * * @paramsource* @param dest Destination */ public static void copyFileByFileChannels(Filesource, 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(); }}} 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

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

conclusion

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 the hot update technology of Flutter is mature, application development may only need to realize the encapsulation of local business function modules on Android and IOS terminals, and put the business and UI codes into Flutter, so as to truly move a piece of business code on both ends. It also gives the product the ability to dynamically deploy APP content without affecting user experience.

Android learning PDF+ architecture video + interview document + source notes


Thank you for being patient enough to read my long-winded article

Here I also share a self-collected and organized Android learning PDF+ architecture video + interview document + source notes, and advanced architecture technology advanced brain map, Android development interview topic information, advanced architecture information to help you learn to improve the advanced, but also save you on the Internet search information time to learn, You can also share it with your friends to learn together

If you have the need, you can like + comment, follow me