Flutter动态化探索

作者: zhengxiaoyong | 来源:发表于2019-03-11 10:46 被阅读12次

    前言

    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

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


    image

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

    libflutter.so
    flutter_assets/app.flx(Deprecated)
    flutter_assets/vm_snapshot_data
    flutter_assets/vm_snapshot_instr
    flutter_assets/isolate_snapshot_data
    flutter_assets/isolate_snapshot_instr
    flutter_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包,下发后合成,最终完成替换更新,这种方案是可行的

    关注我

    image

    相关文章

      网友评论

        本文标题:Flutter动态化探索

        本文链接:https://www.haomeiwen.com/subject/spjvpqtx.html