Following Tencent, another gangster updates to Flutter hot hands

Author: Big Brother

Foreword

Following Tencent, another gangster updates to Flutter hot hands

Flutter official statement on GitHub hot update is not supported, but in Flutter source where there is a relevant part of the code embedded heat update, and by some of our own means, in the end is Android enables dynamic updates functional.

Flutter product inquiry

Whether you are creating a complete Flutter project, or Native way to get Moudle integrated Flutter, also, or Native aar way to integrate Flutter, Flutter are based on Native final project + UI product Flutter exists in Andorid App end of. So here open a package aar Flutter generating compiled in release mode do Analysis:

Following Tencent, another gangster updates to Flutter hot hands

The product focus of our attention in assets, jni, libs directory in which three other documents are Nactive layer shell works;

JNI : the directory of file libflutter.so, the file is C Flutter Engine (engine) ++ implementation layer, providing Skia (rendering engine), Dart, Text (texture mapping) to support the like;

libs : the presence of files in the directory is flutter.jar, the file is Flutter embedding (embedded) layer Java implementation, this layer provides a platform to support many functions Flutter Native layer, such as creating a thread.

Assets : the directory is divided into two portions:

  1. flutter_assets directory: storage resource Flutter our application layer of the directory, including images, font and so on;
  2. isolate_snapshot_data,isolate_snapshot_instr,vm_snapshot_data,vm_snapshot_instr 文件:这 4 个文件分别对应 isolate,VM 的数据段和指令段文件。这四个文件就是我们自己的 Flutter 代码的产物了。

Flutter 代码的热更

###探究

在我们的 Native 项目中,会在 FlutterMainActivity 中,通过调用 Flutter 这个类来创建 View:

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

查看 Flutter 类代码,发现 Flutter 类主要做了几件事:

  1. 使用 FlutterNative 加载 View,设置路由,使用 lifecycle 绑定生命周期;
  2. 使用 FlutterMain 初始化,重点关注这里。
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);
}

所以,真正初始化的相关代码是在 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);
 }
}

在 startInitialization 中,主要执行了三个初始化方法 initConfig (applicationContext),initAot (applicationContext),initResources (applicationContext),最后记录了执行时间;

在 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);
 }
}

在 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();

在 ResourceExtractor 类中,通过名字就能知道这个类是做资源提取的。把 add 的 Flutter 相关文件从 assets 目录中取出来,该类中 ExtractTask 的 doInBackground 方法中:

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

这句话指定了资源提取的目的地,即 data/data/ 包名 /app_flutter,如下:

Following Tencent, another gangster updates to Flutter hot hands

如图,可以看到该目录是的访问权限是可读可写,所以理论上,我们只要把自己的 Flutter 产物下载后,从内存 copy 到这里,便能够实现代码的动态更新。

代码实现

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"); 
/** 
* Flutter 代码热更新第一步:解压 Flutter 的压缩文件 
*/ 
public static void unZipFlutterFile() { 
 Log.i(TAG, "unZipFile: Start"); 
 try { unZipFile(zipPath, targetDirPath); 
 Log.i(TAG, "unZipFile: Finish"); 
 } catch (Exception e) { 
 e.printStackTrace(); 
 } 
} 
/** 
* Flutter 代码热更新第二步:将 Flutter 的相关文件移动到 AppData 的相关目录,APP启动时调用 
* * @param mContext 获取 AppData 目录需要 
*/ 
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"); 
} 

 /** 
 * 解压缩文件到指定目录 * 
 * @param zipFileString 压缩文件路径 
 * @param outPathString 目标路径 
 * @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(); 
 } 
 } 

 /** 
 * 使用FileChannels复制文件。
 * * @param source 原路径 
 * @param dest 目标路径 
 */ 
 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(); 
 } 
 }
} 
 /** 
 * 更新媒体库 
 * * @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); 
 }}

Flutter 资源的热更新

我们的 App 安装到手机上后,是很难再修改 Assets 目录下的资源,所以关于资源的替换,目前的方案是使用 Flutter 的 API :Image.file () 来从存储卡中读取图片。

通常我们的 Flutter 项目中应当存有关于 App 的图片,尽量保证在热更新的时候使用已经存在的图片,

其次,我们可以使用 Image.network () 来加载网络资源的图片,

如果还不能满足需求,兜底的方案就是使用 Image.file (),将资源图片放到 Zip 目录下一起下发,并在 Flutter 代码中使用 Image.file () 来加载。

  1. 通过 Native 层方法拿到图片文件夹的内存地址 dataDir;
  2. 判断图片是否存在,存在则加载,不存在则加载已经存在的图片占位;
new File(dataDir + 'hotupdate_test.png').existsSync()
 ? Image.file(new File(dataDir + 'hotupdate_test.png'))
 : Image.asset("images/net_error.png"),

总结

在 Flutter 代码产物替换中,因为替换的 4 个文件皆为直接加载到内存中的引擎代码,所以这部分优化空间有限。但在资源的热更新中,资源是从 Assets 取得,所以这里应该有更优的方案。

Flutter 的热更新意味着可以在在 App 的一个入口里,像 H5 一样无穷的嵌入页面,但又有和原生媲美的流畅体验。

未来 Flutter 热更新技术如果成熟,应用开发可能只需要 Android 端和 IOS 端实现本地业务功能模块的封装,业务和 UI 的代码都放在 Flutter 中,便能够真正的实现移动两端一份业务代码,并且赋予产品在不影响用户体验的情况下,拥有动态部署 APP 内容的能力。

Android学习PDF+架构视频+面试文档+源码笔记


感谢大家能耐着性子,看完我啰哩啰嗦的文章

Here I also share a copy of your collection of finishing Android studying architecture PDF + Video + Interview + document source notes , as well as advanced technical architecture Advanced Brain Mapping, Android interview with thematic development, advanced materials advanced architecture to help you enhance Advanced Learning , but also saves everyone time online in search of information to learn, you can also share with close friends studying together

If you have a need, we can point like + comment , concern me
Following Tencent, another gangster updates to Flutter hot hands

Guess you like

Origin blog.51cto.com/14573572/2450607