Flutter 混合架构方案探索

2023-11-02

得益于 Flutter 优秀的跨平台表现,混合开发在如今的 App 中随处可见,如最近微信公布的小程序新渲染引擎 Skyline 发布正式版也在底层渲染上使用了 Flutter,号称渲染速度提升50%。

在现有的原生 App 中引入 Flutter 来开发不是一件简单的事,需要解决混合模式下带来的种种问题,如路由栈管理、包体积和内存突增等;另外还有一种特殊的情况,一个最初就由 Flutter 来开发的 App 也有可能在后期混入原生 View 去开发。

我所在的团队目前就是处于这种情况,Flutter 目前在性能表现上面还不够完美,整体页面还不够流畅,并且在一些复杂的页面场景下会出现比较严重的发热行为,尽管目前 Flutter 团队发布了新的渲染引擎 impeller,它在 iOS 上表现优异,流畅度有了质的提升,但还是无法完全解决一些性能问题且 Android 下 impeller 也还没开发完成。

为了应对当下出现的困局和以后可能出现的未知问题,我们期望通过混合模式来扩宽更多的可能性。

 

路由管理

混合开发下最难处理的就是路由问题了,我们知道原生和 Flutter 都有各自的路由管理系统,在原生页面和 Flutter 页面穿插的情况下如何统一管理和互相交互是一大难点。目前比较流行的单引擎方案,代表框架是闲鱼团队出品flutter_boost;flutter 官方代表的多引擎解决方案 FlutterEngineGroup

单引擎方案 flutter_boost

flutter_boost 通过复用 Engine 达到最小内存的目的,下面这张图是它的设计架构图(图片来自官方

在引擎处理上,flutter_boost 定义了一个通用的 CacheId:"flutter_boost_default_engine",当原生需要跳转到 Flutter 页面时,通过FlutterEngineCache.getInstance().get(ENGINE_ID); 获取同一个 Engine,这样无论打开了多少如图中的 A、B、C 的 Flutter 页面时,都不会产生额外的Engine内存损耗。

public class FlutterBoost {
    public static final String ENGINE_ID = "flutter_boost_default_engine";
    ...
}

另外,双端都注册了导航的接口,通过Channel来通知,用于请求路由变化、页面返回以及页面的生命周期处理等。在这种模式下,这一层Channel的接口处理是重点。

多引擎方案 FlutterEngineGroup

为了应对内存爆炸问题,官方对多引擎场景做了优化,FlutterEngineGroup应运而生,FlutterEngineGroup下的 Engine 共用一些通用的资源,例如GPU 上下文、线程快照等,生成额外的 Engine 时,号称内存占用缩小到 180k。这个程度,基本可以视为正常的损耗了。

以上图中的 B、C 页面为例,两者都是 Flutter 页面,在 FlutterEngineGroup 这种处理下,因为它们所在的 Engine 不是同一个,这会产生完全的隔离行为,也就是 B、C 页面使用不同的堆栈,处在不同的 Isolate 中,两者是无法直接进行交互的。

多引擎的优点是:它可以抹掉上图所示的 F、E、C 和 D、A 等内部路由,每次新增 Flutter 页面时,全部回调到原生,让原生生成新的 Engine 去承载页面,这样路由的管理全部由原生去处理,一个 Engine 只对应一个 Flutter 页面。

但它也会带来一些额外的处理,像上面提到的,处在不同 Engine 下的Flutter 页面之间是无法直接交互的,如果涉及到需要通知和交互的场景,还得通过原生去转发。

关于FlutterEngineGroup的更多信息,可以参考官方说明

性能对比

官方号称 FlutterEngineGroup 创建新的 Engine 只会占用 180k 的内存,那么是不是真就如它所说呢?下面我们来针对上面这两种方案做一个内存占用测试

flutter_boost

测试机型:OPPO CPH2269

测试代码:github.com/alibaba/flu…

内存 dump 命令: adb shell dumpsys meminfo com.idlefish.flutterboost.example

条件 PSS RSS 最大变化
1 Native 88667 165971
+26105 +28313 +27M
1 Native + 1 Flutter 114772 194284
-282 +1721 +1M
2 Native + 2 Flutter 114490 196005
+5774 +5992 +6M
5 Native + 5 Flutter 120264 201997
+13414 +14119 +13M
10 Native + 10 Flutter 133678 216116

第一次加载 Flutter 页面时,增加 27M 左右内存,此后多开一个页面内存增加呈现从 1M -> 2M -> 2.6 M 这种越来越陡的趋势(数值只是参考,因为其中有 Native 页面,只看趋势变化上看)

FlutterEngineGroup

测试机型:OPPO CPH2269

测试代码:github.com/flutter/sam…

内存 dump 命令: adb shell dumpsys meminfo dev.flutter.multipleflutters

条件 PSS RSS 最大变化
1 Native 45962 140817
+29822 +31675 +31M
1 Native + 1 Flutter 75784 172492
-610 +2063 +2M
2 Native + 2 Flutter 75174 174555
+7451 +7027 +3.7M
5 Native + 5 Flutter 82625 181582
+8558 +7442 +8M
10 Native + 10 Flutter 91183 189024

第一次加载 Flutter 页面时,增加 31M 左右内存,此后多开一个页面内存增加呈现从 1M -> 1.2M -> 1.6 M 这种越来越陡的趋势(数值只是参考,因为其中有 Native 页面,只看趋势变化上看)

结论

两个测试使用的是不同的 demo 代码,不能通过数值去得出孰优孰劣。但通过数值的表现,我们基本可以确认,两个方案都不会带来异常的内存暴涨,完全在可以接受的范围。

PlatformView

PlatformView 也可实现混合 UI,Flutter 中的 WebView 就是通过 PlatformView 这种方式引入的。

PlatformView 允许我们向 Flutter 界面中插入原生 View,在一个页面的最外层包裹一层 PlatformView,路由的管理都由 Flutter 来处理。这种方式下没有额外的 Engine 产生,是最简单的混合方式。

但它也有缺点,不适合主 Native 混 Flutter 的场景,而现在大多都是以主 Native 混 Flutter的场景为主。另外,PlatformView 因其底层实现,会出现兼容性问题,在一些机型下可能会出现键盘问题、闪烁或其它的性能开销,具体可看这篇介绍

数据共享

原生和 Flutter 使用不同的开发语言去开发,所以在一侧定义的数据结构对象和内存对象对方都无法感知,在数据同步和处理上必须使用其它手段。

MethodChannel

Flutter 开发者对 MethodChannel 一定不陌生,开发当中免不了跟原生交互,MethodChannel 是双向设计,即允许我们在 Flutter 中调用原生的方法,也允许我们在原生中调用 Flutter 的方法。对 Channel 不太了解的可以看一下官方文档,如文档中提到的,这个通道传输的过程中需要将数据编解码,对应的关系以kotlin为例(完整的映射可以查看文档):

Dart                         | Kotlin      |
| -------------------------- | ----------- |
| null                       | null        |
| bool                       | Boolean     |
| int                        | Int         |
| int, if 32 bits not enough | Long        |
| double                     | Double      |
| String                     | String      |
| Uint8List                  | ByteArray   |
| Int32List                  | IntArray    |
| Int64List                  | LongArray   |
| Float32List                | FloatArray  |
| Float64List                | DoubleArray |
| List                       | List        |
| Map                        | HashMap     |

本地存储

这种方式比较容易理解,将本地存储视为中转站,Flutter中将数据操作存储到本地上,回到原生页面时在某个时机(如onResume)去查询本地数据库即可,反之亦然。

问题

不管是MethodChannel或是本地存储,都会面临一个问题:对象的数据结构是独立的,两边需要重复定义。比如我在 Flutter 中有一个 Student 对象,Android 端也要定义一个同样结构的 Student,这样才能方便操作,现在我将Student student转成Unit8List传到Android,Channel中解码成Kotlin能操作的ByteArray,再将ByteArray转译成AndroidStudent对象。

class Student {
  String name;
  int age;
  Student(this.name, this.age);
}

对于这个问题最好的解决办法是使用DSL一类的框架,如Google的ProtoBuf,将同一份对象配置文件编译到不同的语言环境中,便能省去这部分双端重复定义的行为。

图片缓存

在内存方面,如果同样的图片在两边都加载时,会使得原生和 Flutter 都会产生一次缓存。在 Flutter 下默认就会缓存在ImageCache中,原生下不同的框架由不同的对象负责,为了去掉重复的图片缓存,势必要统一图片的加载管理。

阿里的方案也是如此,通过外接原生图片库,共享图片的本地文作缓存和内存缓存。它的实现思路是通过自定义ImageProviderCodec,对接外部图库,获取到图片数据做解析,对接的处理是通过扩展 Flutter Engine。

如果期望不修改Flutter Engine,也可通过外接纹理的方式去处理。通过PlatformChannel去请求原生,使到图片的外接纹理数据,通过TextTure组件展示图片。

// 自定义 ImageProvider 中,通过 Channel 去请求 textureId
var id = await _channel.invokeMethod('newTexture', {
  "imageUrl": imageUrl,
  "width": width ?? 0,
  "height": height ?? 0,
  "minWidth": constraints.minWidth,
  "minHeight": constraints.minHeight,
  "maxWidth": constraints.maxWidth,
  "maxHeight": constraints.maxHeight,
  "cacheKey": cacheKey,
  "fit": fit.index,
  "cacheOriginFile": cacheOriginFile,
});

// ImageWidget 中展示时通过 textureId 去显示图片
SizedBox(
  width: width,
  heigt: height,
  child: Texture(
    filterQuality: FilterQuality.high,
    textureId: _imageProvider.textureId.value,
  ),
)

总结

不同业务对于混合的程度和要求有所要求,并没有万能的方案。比如我团队的情况就是主Flutter混原生,在路由管理上我选择了PlatformView这种处理模式,这种方式更容易开发和维护,后期如果发现有兼容性问题,也可过渡到flutter_boostFlutterEngineGroup上。

参考:https://juejin.cn/post/7262616799219482681

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter 混合架构方案探索 的相关文章

  • 如何替换 Android 中已弃用的 Bundle/Argument get(key) 调用

    我有以下扩展函数 允许我在应用程序活动和片段之间传递捆绑数据项 inline fun
  • 如何在MaterialApp主题中设置fontSize的MediaQuery textScaleFactor

    我想在 MaterialApp 主题内设置 MediaQuery 以便在用户更改设置时 FontSize 尊重用户设置 我尝试创建最终的curlScale MediaQuery of context textScaleFactor 并相应地
  • 在 ChromeO 上安装未知来源的 apk

    我今天早上更新了我的 Chromebook Asus Flip 以获取 Play 商店 我的 Chromebook 安装了 M53dev 通道版本 它运作良好 我可以安装并运行从 Play 商店下载的 Android 应用程序 我想测试我的
  • 使用 Fragment 在工具栏中实现 SearchView

    当前情况 我的应用程序主页由导航抽屉组成 因此我将视图作为片段加载 我的工具栏中也有搜索图标 我在中实现了它menu xml 下一步我实施了SearchView通过以下问题的答案来获取搜索图标在工具栏中实现搜索 https stackove
  • Android 图表[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个项目 其中有一些图表 图形 刻度图 烛台图和范围图 但问题是 没有该图表的库 我有烛台图的
  • 在 gradle 中,我应该排除分支下的所有依赖项还是只排除根就足够了?

    我已将以下自定义任务添加到我的build gradlefile 为了打印出依赖项的依赖项 This part is useful for finding conflict resolution s between dependencies
  • 如何通过我的活动在 Android 中设置铃声?

    我正在尝试找到一种方法来通过 Android 活动中的代码设置新的默认铃声 我已经将铃声下载到bytearray 最后 我设法将默认铃声设置为我下载的铃声 下面不包含下载代码 仅包含将其设置为默认铃声所需的代码 File k new Fil
  • 吉夫伦致命信号11

    我正在尝试使用一些本机代码来创建 Gif 我使用绘画绘制图像 创建一些笔画 单击 保存 绘制的图像将保存为 JPG 格式 当我单击 创建 Gif 时 它会获取所有图像并开始创建 gif 这是当我收到致命信号 11 并且应用程序重新启动时 我
  • Android 全屏对话框确认和拒绝操作

    材料设计中的全屏对话框应该在操作栏 工具栏上有确认和拒绝操作 我的问题是 我该怎么做 显示对话框 getFragmentManager beginTransaction add R id container new MyDialogFrag
  • HMS 核心地图套件在我的 Android 应用程序上根本无法工作

    我正在尝试在我的应用程序中使用华为 HMS 地图套件 我对整体地图很陌生 无论是来自谷歌还是华为 我按照文档中的教程以及华为提供的代码实验室中的说明进行操作 并将我的代码在一起 但是当我运行地图活动时 什么也没有出现 我得到的只是一个空白活
  • ExpandableListview OnGroupClickListener 未触发

    我正在关注这个 以编程方式折叠 ExpandableListView 中的组 https stackoverflow com questions 4314777 programmatically collapse a group in ex
  • 如何禁用操作栏上“向上”按钮的翻转?

    背景 我做了一个 应用程序管理器 https play google com store apps details id com lb app manager 替代应用程序 我希望添加 RTL 从右到左 语言的翻译 因为我知道在某些 And
  • 透明 9patch 图像:显示出线条

    我得到了一个透明的 9 补丁图像 其中有 9 条补丁线显示槽 This is the output 显然我不希望水平线可见 这就是我创建 9patch 的方式 This is the final image that is used in
  • 在新的 intel x86 android 模拟器中访问 google api

    我只是尝试在新的 x86 android 模拟器中运行我公司的应用程序 但是我们的应用程序依赖于 google 地图 API 而这在 google 随 android sdk 版本 17 提供的 x86 系统映像中不可用 我的直觉告诉我答案
  • 控制 OverlayItem 大小

    我正在构建一个在单个 ItemizedOverlay 中包含几十个 OverlayItems 的地图 我的地图设计为可以非常近距离地查看 大约缩放级别 18 并且 OverlayItems 彼此非常接近 地图放大时看起来不错 但是 如果用户
  • onTaskRemoved() 在华为和小米设备中没有被调用

    我一直在使用onTaskRemoved 服务中的方法 用于检测应用程序何时通过滑动从设备最近列表中删除 我执行一些日志记录和发生这种情况时需要执行的一些其他操作 它工作完美 然后我在运行Android 6 0的华为设备上检查了这个方法 该方
  • 离子初始加载时间

    我正在使用 Ionic 构建一个简单的应用程序 但我的应用程序在冷启动时的初始加载时间方面存在性能问题 这是我所做的 collection repeat 代替带有 track by 的 ng repeat 原生滚动 overflow scr
  • ECDH使用Android KeyStore生成私钥

    我正在尝试使用 Android KeyStore Provider 生成的私有文件在 Android 中实现 ECDH public byte ecdh PublicKey otherPubKey throws Exception try
  • 从文件路径显示图像视图?

    我需要仅使用文件名而不是资源 ID 来显示图像 ImageView imgView new ImageView this imgView setBackgroundResource R drawable img1 我在可绘制文件夹中有图像
  • 如何访问我的 Android 程序中的联系人

    我正在制作一个短信应用程序 并且想要访问我的 Android 应用程序中的联系人 我想访问联系人 就像他们在实际联系人列表中一样 选择后 我需要返回到我的活动 在其中我可以向该人发送短信 或者是否可以访问存储联系人的数据库 我的代码如下所示

随机推荐

  • 杀毒软件 clamav 的安装和使用

    目录 一 clamAV介绍 二 安装ClamAV clamdscan 三 手动更新数据库 四 用法 4 1 clamscan用法 4 2 clamdscan用法 五 python判定有无检测出病毒 一 clamAV介绍 ClamAV 杀毒是
  • python入门基础-数据类型&有序序列和无序序列;

    目录 python优点 python缺点 python应用场景 Python数据类型 字符串 string 列表 list 元组 tuple 不可变数据 1 2 3 set 集合 1 2 3 无序 自动去重 dict字典 key value
  • 基于YOLO的3D人脸关键点检测方案

    目录 前言 一 任务列表 二 3D人脸关键点数据 H3WB 2 下载方法 3 任务 4 评估 5 使用许可 3DFAW AFLW2000 3D 三 3D关键点的Z维度信息 1 基于3DMM模型的方法 2 H3WB 四 当前SOTA的方法 1
  • STM32串口下载

    使用FlyMCU下载程序 1 上电前 设置BOOT0 1 BOOT1 0 或者是在上电后 设置BOOT0 1 BOOT1 0之后 然后按一下复位按键 从而通过串口下载程序 2 在MDK编译加载生成的hex文件 并勾选右边的编程前重装文件 这
  • Unity进阶 - 动画系统 - Animations选项卡

    Unity进阶 动画系统 Animations选项卡 相关文章阅读 Unity 进阶 动画系统 Mecanim动画系统 Unity进阶 动画系统 给人物角色制作动画 Unity进阶 动画系统 人形动画的导入 Unity进阶 动画系统 导入设
  • 中国移动笔试有感

    被3号的中国移动笔试给深深地虐了 以前被像阿里阿这样的大企业虐就没啥子感觉 笔试完依旧是嘻嘻哈哈 像恒生这样的企业 笔试对我来说还是比较容易的虽然面试被刷了 说明还是需要多读书啊 但是中国移动的笔试 真的深深的激起了我学习的动力 我也不知道
  • C语言_带参宏和函数的区别及各自优缺点

    前言 C语言中 要想解决某子问题 可以自定义一个函数来专门处理该问题 比如 我想比较两个数的大小 那么我可以地定义一个函数max来完成两个数求最大值功能 但是 C语言中我们也可以通过宏定义来定义一个求最值的函数MAX 然后通过使用带参宏来完
  • 善待自己:改变命运的N个人生哲理

    心灵的栅栏 人与月亮的距离并不遥远 因为人与人心灵间的距离更为遥远 王尔德 当玛格丽特的丈夫杰瑞因脑瘤去世后 她变得异常愤怒 生活太不公平 她憎恨孤独 孀居 年 她的脸变得紧绷绷的 一天 玛格丽特在小镇拥挤的路上开车 忽然发现一幢她喜欢的房
  • 《软件测试的艺术》读后感 Or 读书笔记

    软件测试的艺术 读后感 Or 读书笔记 第一章 一次自评价测试 第二章 软件测试的心理学和经济学 第三章 代码检查 走查与评审 第四章 测试用例的设计 第五章 模块 单元 测试 第六章 更高级别的测试 第七章 可用性 或用户体验 测试 第八
  • Java—类的加载概述

    1 1 类的加载概述 当程序要使用某个类时 如果该类还未被加载到内存中 则系统会通过加载 连接 初始化三步来实现对这个类进行初始化 1 加载 是将class文件读入内存 并为之创建一个Class对象 任何类被使用时系统都会建立一个Class
  • Docker中安装Gitlab详细全教程

    前言 一 安装Gitlab 1 搜索影像 2 下载影像 3 启动Git服务 4 查看Gitlab是否已经启动 二 配置Gitlab 1 首先 先进入容器 2 修改gitlab rb文件 3 修改gitlab rb文件中的IP与端口号 3 配
  • OpenCV——求直线交点

    您可以使用OpenCV中的cv Point和cv Vec4i数据类型来表示点和直线 然后使用cv intersect函数来计算两条直线的交点 下面是一个示例代码 其中line1和line2分别表示两条直线的起点和终点 cv Point li
  • 机器学习(11)——时间序列分析

    目录 1 时间序列数据的相关检验 1 1 白噪声检验 1 2 平稳性检验 1 3 自相关分析和偏自相关分析 2 移动平均算法 2 1 简单移动平均法 2 2 简单指数平滑法 2 3 霍尔特线性趋势法 2 4 Holt Winters 季节性
  • Centos7安装dig命令

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Centos7安装dig命令 作者 jwj 时间 2018 10 17 分类 服务器 最近做一个项目 需要用到Gmail邮箱发送邮件 但发现发送不出去 排查问题时 需要用到
  • 最新SecureCRT 中文注册版

    SecureCRT是一款由VanDyke Software公司开发的终端仿真软件 它提供了类似于Telnet和SSH等协议的远程访问功能 SecureCRT专门为网络管理员 系统管理员和其他需要保密访问网络设备的用户设计 软件下载 Secu
  • filter IE滤镜(Internet Explorer)CSS

    http justcoding iteye com blog 940184 概述 CSS滤镜虽然只能在IE浏览器中表现出效果 但是仍不失为网页增加特效的好办法 1 CSS静态滤镜样式 filter CSS静态滤镜样式的使用方法 filter
  • PlatformIO基于ESP32S2的SPI软串口LCD屏调试

    文章目录 VSCode PlatformIO Arduino ESP32S2 SPI LCD 320 240 一 准备工作 二 测试 1 全屏检测 2 图形测试 参考资料 VSCode PlatformIO Arduino ESP32S2
  • 基于SSM+Vue的网上拍卖系统

    末尾获取源码 开发语言 Java Java开发工具 JDK1 8 后端框架 SSM 前端 采用Vue技术开发 数据库 MySQL5 7和Navicat管理工具结合 服务器 Tomcat8 5 开发软件 IDEA Eclipse 是否Mave
  • Halcon: (示例 1)OCR 字符识别

    目录 示例 涉及算子描述 text line orientation hom mat2d identity hom mat2d rotate affine trans image dots image reduce domain vecto
  • Flutter 混合架构方案探索

    得益于 Flutter 优秀的跨平台表现 混合开发在如今的 App 中随处可见 如最近微信公布的小程序新渲染引擎 Skyline 发布正式版也在底层渲染上使用了 Flutter 号称渲染速度提升50 在现有的原生 App 中引入 Flutt