本文由 句号君 授权投稿
原文链接:https://blog.csdn.net/qizewei123/article/details/102963340

Flutter 官方在 GitHub 上声明是暂时不支持热更新的,但是在 Flutter 的源码里,是有一部分预埋的热更新相关的代码,并且通过一些我们自己的手段,在Android端是能够实现动态更新的功能的。

Flutter 产物的探究

不论是创建完全的 Flutter项目,还是 Native以 Moudle的方式集成 Flutter,亦或是 Native以 aar方式集成  Flutter,最终  Flutter在 Andorid端的 App 都是以 Native项目+ Flutter 的UI产物存在的。所以在这里拆开一个 Flutter在 release模式下编译后生成 aar包来做分析:

我们关注重点在 assets,jni,libs 这 3 个目录中,其他的文件都是 Nactive层壳工程的产物。

jni :该目录下存在文件 libflutter.so,该文件为 Flutter Engine (引擎) 层的 C++实现,提供skia(绘制引擎),Dart,Text(纹理绘制)等支持。

libs:该目录下存在文件为 flutter.jar,该文件为 Flutter embedding (嵌入) 层的 Java实现,该层提供给 Flutter 许多Native层平台系统功能的支持,比如创建线程。

assets:该目录下分为两部分:

  1. flutter_assets 目录:该目录下存放Flutter 我们应用层的资源,包括images,font等

  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,如下:

如图,可以看到该目录是的访问权限是可读可写,所以理论上,我们只要把自己的 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() 来加载。

  • 通过 Native 层方法拿到图片文件夹的内存地址 dataDir

  • 判断图片是否存在,存在则加载,不存在则加载已经存在的图片占位

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内容的能力。

推荐阅读
如何写出让同事好维护的代码?
一线大厂的程序员职级对标
真正的强者,敢于在寒冬里裸辞

编程·思维·职场
欢迎扫码关注

flutter不支持热更新_Flutter 在安卓上可以实现热更新了相关推荐

  1. flutter已经支持安卓热更新_flutter 在 android 上的热更新

    热更新是一种需求吧. 自然会想到flutter 是否支持热更新. 然后一些群里问了问普遍反映不可以热更新,还说咸鱼的文章写了不支持热更新. 然后我表示很怀疑. 我的结论可以做到热更新 1.你需要把fl ...

  2. linux支持ppp网卡,linux和安卓上(ppp widget)使用3g网卡的方法

    在usb3G上网卡中,有许多都是使用的称为zeroCD的一种模式.就是在初始时,将3G上网卡,识别为一个CD设备.然后自动安装驱动,安装完成后.自动卸载.将3G网卡转换成串口模式. 要想在linux和 ...

  3. 知乎热榜是什么?怎样上知乎热榜?

    一.知乎热榜是什么? 我们常见的在品牌的规划中常见的知乎热榜是知乎的一个内容板块,这个热榜里面聚集了知乎全站讨论度最高并且最有价值的内容.最重要的是知乎的热榜面对每一个用户所显示的内容都是统一的,在很 ...

  4. 红米android6版本暂未支持,红米6 Pro获得MIUI系统更新:加入安卓安全补丁!

    原标题:红米6 Pro获得MIUI系统更新:加入安卓安全补丁! 如今,就华为.小米.OPPO.vivo等智能手机厂商,不仅在硬件配置上不断升级,也在软件系统上持续更新优化,以此带给用户更良好的使用体验 ...

  5. 微信小程序 长按图片不出现菜单_微信更新,新功能上了热搜

    微信在推出新功能方面相当克制,但每一次总能引起全网关注. 昨天,微信又因为一个小功能的改进再次上了热搜,在安卓最新的 7.0.17 版本当中,微信取消了两分钟内删除功能. 在新版微信中,发出的消息在两 ...

  6. 安卓插件化与热修复的选型

    参考文章: 安卓插件化的过去现在和未来 张涛 http://kymjs.com/code/2016/05/04/01 安卓插件化从入门到放弃 包建强 http://www.infoq.com/cn/n ...

  7. 小米10性能再进化!小米带来国内安卓系统GPU驱动首次更新——初阶GPU及其驱动升级认识

    去年12月,高通宣布率先开放 GPU 驱动更新,骁龙865.骁龙765等系列将在后期获得公开的 GPU 驱动更新,首批支持的机型有小米10/10Pro和 Redmi K30 Pro等.基于此点,前些时 ...

  8. Flutter Web 支持现已进入稳定版

    作者 / Mariam Hasnany, Product Manager, Flutter 我们对 Flutter 的愿景是成为一个可移植的 UI 框架,在全平台上构建精美的应用体验.做为 Flutt ...

  9. Android热更新十:自己写一个Android热修复

    很早之前就想深入的研究和学习一下热修复,由于时间的原因一直拖着,现在才执笔弄起来. Android而更新系列: Android热更新一:JAVA的类加载机制 Android热更新二:理解Java反射 ...

最新文章

  1. 计算机名称改变之后,HOUDINI Server 连接不上的解决办法
  2. conflicts with existing, non-compatible bean definition of same name and class
  3. 数据仓库与联机分析处理
  4. pandas使用isna函数和any函数检查dataframe是否包含缺失值、整体是否有缺失值,不区分行列(check if dataframe contains any missing values
  5. 学维修电脑要多久_学蛋糕一般要学习多久、学费贵吗?
  6. app服务器一种什么样的服务器
  7. C# 导出 Excel 数字列出现‘0’的解决办法
  8. C++泛型编程实现平衡二叉搜索树AVL
  9. 【2015年第4期】大数据时代的数据挖掘 —— 从应用的角度看大数据挖掘(下)...
  10. 基于逻辑回归算法模型搭建思路
  11. Linux软raid创建
  12. 我们常用的软件测试工具有哪些?
  13. c语言数制转换程序,数制转换(C语言实现)
  14. IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! 解决办法
  15. Java Web研发工程师新手入门指南
  16. 泛目录站群,黑帽SEO程序,自由搭配任意泛,百科程序
  17. 基于Visual Studio2010开发office2010办公自动化应用(12)自定义VisioAddIn插件
  18. 7 士兵排队---PTA(排序+中位数)(C++)
  19. 对你的孩子再多一点耐心——一位美国父亲写给儿子的一封信
  20. 安卓手机解压缩软件哪个好用

热门文章

  1. 本月初 本月末 java_本月内容作家(2018年8月)
  2. ajax 跨站返回值,jquery ajax 跨域问题
  3. php serialize error at offset,PHP Notice: unserialize(): Error at offset XX of XX bytes
  4. 手机怎么能把书签导出来_成人高考能在手机上报名吗?成人高考怎么缴费?
  5. scala中字符串计数_如何在Scala中创建一系列字符?
  6. 4种分布式Session的实现方式!老大直呼666...
  7. C# Winform 窗体美化(五、鼠标穿透)
  8. Qt QtCreator 所有版本官方下载地址
  9. 10分钟带你学会微信小程序的反编译
  10. IDEA写sql语句的时候没有提示信息的处理办法