Flutter动态化探索

4,083 阅读5分钟

前言

Flutter的动态化,对于Android而言,一个很清晰的思路就是动态替换flutter_assets的所有资源文件,因为Flutter加载代码和资源的工作目录即是应用沙盒目录下的 app_flutter目录,我们把这个目录下的文件进行对应替换即可,而对于IOS,由于本身系统的限制,官方目前也没相应方案,所以目前暂且说下Android平台上的Dynamic Patch

而目前Flutter Engine最新的Master分支上支持Flutter引擎的动态更新,所以Dynamic Patch支持JIT与AOT模式下的所有代码产物与资源的动态更新,以及模式互切,即下述文件:

isolate_snapshot_data :App代码数据段isolate_snapshot_instr :App代码指令段vm_snapshot_data :VM虚拟机数据段vm_snapshot_instr :VM虚拟机指令段kernel_blob.bin :Dart代码产物flutter.so :Flutter引擎assets :资源文件

在进行Flutter初始化时,做了动态加载Flutter引擎的支持:

        if (sResourceUpdater == null) {            System.loadLibrary("flutter");        } else {            sResourceExtractor.waitForCompletion();            File lib = new File(PathUtils.getDataDirectory(applicationContext), DEFAULT_LIBRARY);            if (lib.exists()) {                System.load(lib.getAbsolutePath());            } else {                System.loadLibrary("flutter");            }        }

以及,对于icudtl.dat字符编码表打进了 flutter.so引擎中,这一改变主要是减少了每次打包需要将icudtl.dat它复制到 flutter_assets中维护成本,以及避免了Flutter在加载时把它拷贝到app_flutter时发生错误的风险,这对动态更新也更加方便了,Flutter引擎即包含了它,构建脚本如下:

action("icudtl_object") {  script = "$flutter_root/sky/tools/objcopy.py"  icudtl_input = "//third_party/icu/flutter/icudtl.dat"  icudtl_output = "$root_build_dir/flutter_icu/icudtl.o"  inputs = [    "$icudtl_input",  ]  outputs = [    "$icudtl_output",  ]  args = [    "--objcopy", rebase_path(android_objcopy),    "--input", rebase_path(icudtl_input),    "--output", rebase_path(icudtl_output),    "--arch", current_cpu,  ]}

Flutter官方动态化流程

相关配置

Config Patch Server Url

Application节点下的MetaData属性,对应Key为PatchServerURL

uri = new URI(metaData.getString("PatchServerURL") + "/" + getAPKVersion() + ".zip");

Config Patch Download Mode

Application节点下的MetaData属性,对应Key为PatchDownloadMode

String patchDownloadMode = metaData.getString("PatchDownloadMode");

Config Patch Install Mode

Application节点下的MetaData属性,对应Key为PatchInstallMode

String patchInstallMode = metaData.getString("PatchInstallMode");

Patch Installed Path

    public File getInstalledPatch() {        return new File(context.getFilesDir().toString() + "/patch.zip");    }

Patch Downloaded Path

    File getDownloadedPatch() {        return new File(getInstalledPatch().getPath() + ".install");    }

动态更新流程

Flutter Dynamic Patch全局是通过一个开关判断是否开启了Patch更新特性,即application中的meta-data属性:DynamicPatching

官方内部动态更新流程大致为:

Flutter初始化主要流程为:1、根据所配置项下载Patch文件(异步和同步两种方式)2、检测Patch的下载目录是否有patch.zip.install待安装的Patch文件3、校验待安装Patch文件内的isolate等文件CRC32与构建号是否与App的一致4、在上一步校验成功后,会把待安装的Patch文件重命名并拷贝到Patch安装目录,文件名为 patch.zip5、校验App下的data/packageName/app_flutter/目录的时间戳文件6、在上一步校验成功后则删除该目录下的所有数据、指令集、flutter引擎文件,同时解压patch.zip文件,获取下列文件拷贝至app_flutter目录:

libflutter.soflutter_assets/app.flx(Deprecated)flutter_assets/vm_snapshot_dataflutter_assets/vm_snapshot_instrflutter_assets/isolate_snapshot_dataflutter_assets/isolate_snapshot_instrflutter_assets/kernel_blob.bin

如果其中存在有些文件不存在patch.zip中,则默认取Apk中 assets目录下对应的资源进行补充7、在App下的app_flutter目录下重新生成时间戳文件,文件名称格式为:

res_timestamp-${App.versionCode}-${App.lastUpdateTime}-[${PatchNumber}]-${PatchFile.lastModifiedTime}

8、通过System.loadLibrary动态选择加载flutter.so引擎,如启用了动态更新功能,则首先从app_flutter路径加载,否则默认加载App内的Flutter引擎,届时,Flutter的启动初始化完成9、最后在App运行期如果有FlutterActivity页面的启动,则会进行Flutter引擎的初始化,并把 AppBundle路径(即app_flutter)传递给Flutter引擎供加载Flutter代码和数据资源,即:NativeInit

定制化动态化方案

为什么要屏蔽官方的流程,而自定义一套动态化部署流程?官方的流程以及配置太过于硬核,不能很好的与当前的业务以及动态部署模式结合,如:不支持灰度发布、定向版本部署等

而Flutter对于自定义动态化流程,并未给出对应接口实现,如:Patch下载、校验规则、Patch替换等,所以对于使用我们自己的自定义的动态化流程,需要尽量不改动源码,保证侵入性最小,官方给出了一个metadata的配置来关闭或打开Patch的更新功能DynamicPatching,首先关闭这个,不使用内部的动态更新Patch逻辑,其次,对于动态替换,保持让Flutter先执行初始化替换流程,而自定义的这套的动态替换流程在FlutterMain init之后进行初始化或配置,进而进行资源的替换更新,而flutter.so的加载过程还在内部,这段需要屏蔽,从而动态加载我们下发的Flutter引擎,自定义的动态化加载方案一些流程和校验规则是可以参考官方实现

Flutter差量更新

对于上述动态更新流程,都是基于Flutter资源的完整替换,也即全量,而Flutter的代码产物是比较大的,通常来说有几M,所以Flutter动态化方案中肯定得支持差量的动态更新,一些二进制差量工具如bsdiff、xdelta等,通过对比获取差量Patch包,下发后合成,最终完成替换更新,这种方案是可行的

往期精彩回顾

Flutter Wrapper Workflow

一种Api兼容性检测方案

Flutter混合开发组件化与工程化架构

长按识别二维码关注我