Flutter 混合架构方案探索

news2024/11/15 18:00:16

得益于 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 达到最小内存的目的,下面这张图是它的设计架构图(图片来自官方)

v2-cec9a709ee743c3abd1a2b7949b24a7a_720w.png

在引擎处理上,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

条件PSSRSS最大变化
1 Native88667165971
+26105+28313+27M
1 Native + 1 Flutter114772194284
-282+1721+1M
2 Native + 2 Flutter114490196005
+5774+5992+6M
5 Native + 5 Flutter120264201997
+13414+14119+13M
10 Native + 10 Flutter133678216116

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

FlutterEngineGroup

测试机型:OPPO CPH2269

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

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

条件PSSRSS最大变化
1 Native45962140817
+29822+31675+31M
1 Native + 1 Flutter75784172492
-610+2063+2M
2 Native + 2 Flutter75174174555
+7451+7027+3.7M
5 Native + 5 Flutter82625181582
+8558+7442+8M
10 Native + 10 Flutter91183189024

第一次加载 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/834738.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kubernetes高可用集群二进制部署(四)部署kubectl和kube-controller-manager、kube-scheduler

Kubernetes概述 使用kubeadm快速部署一个k8s集群 Kubernetes高可用集群二进制部署(一)主机准备和负载均衡器安装 Kubernetes高可用集群二进制部署(二)ETCD集群部署 Kubernetes高可用集群二进制部署(三)部署…

解密爬虫ip是如何被识别屏蔽的

在当今信息化的时代,网络爬虫已经成为许多企业、学术机构和个人不可或缺的工具。然而,随着网站安全防护的升级,爬虫ip往往容易被识别并屏蔽,给爬虫工作增加了许多困扰。在这里,作为一家专业的爬虫ip供应商,…

一文了解MySQL中的多版本并发控制作者

最近在阅读《认知觉醒》这本书,里面有句话非常打动我:通过自己的语言,用最简单的话把一件事情讲清楚,最好让外行人也能听懂。 也许这就是大道至简,只是我们习惯了烦琐和复杂。 希望借助今天这篇文章,能用…

关于DC电源模块的噪音问题

BOSHIDA 关于DC电源模块的噪音问题 BOSHIDA DC电源模块是广泛使用的电源模块,它在各个领域中都有应用,例如:电子设备、计算机、通讯等领域。然而,DC电源模块也存在一些噪音问题,这些噪音问题会影响到电子设备的正常运…

SW - 装配图用的组合零件的制作步骤

文章目录 SW - 装配图用的组合零件的制作步骤概述笔记END SW - 装配图用的组合零件的制作步骤 概述 一套相关零件做好后, 需要做装配体, 将零件都装上, 看看是否有纰漏. 如果不做总装图, 真不放心. 万一废了, 耽误的时间大把的. 做总装图的时间比做零件的2个星期比起来, 代价…

C语言进阶——sprintf与sscanf、文件的随机读写(fseek、ftell、rewind)

目录 sprintf与sscanf sprintf sscanf 文件的随机读写 fseek ftell rewind sprintf与sscanf 与之前学习过的进行对比: scanf 是针对标准输入的格式化输入语句 printf 是针对标准输出的格式化输出语句 fscanf 是针对所有输入流的格式化语句 fprintf 是针对所…

深度学习Redis(1):Redis内存模型

前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。 我们使用Redis时,会接触Redis的5种对象类型(字符串、哈希、列表、集合、有序集合…

opencv 39 -形态学梯度运算(膨胀图像减腐蚀图像) cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

形态学梯度运算是用图像的膨胀图像减腐蚀图像的操作,该操作可以获取原始图像中前景图像的边缘。 例如,图 8-20 演示了形态学梯度运算。 从图 8-20 中可以看到,形态学梯度运算使用膨胀图像(扩张亮度)减腐蚀图像&#xf…

怎么把文字转成语音?这两种转换方法轻松转换

在疲劳时阅读变得困难时,可以通过语音听取信息,以减轻眼睛的压力。长时间的阅读可能会对眼睛造成一定的压力,甚至导致疲劳。将文字转换成语音可以让人们在疲劳时听取信息,减轻眼睛的压力,保护视力。怎么把文字转换成语…

Tcp的粘包和半包问题及解决方案

目录 粘包: 半包: 应用进程如何解读字节流?如何解决粘包和半包问题? ①:固定长度 ②:分隔符 ③:固定长度字段存储内容的长度信息 粘包: 一次接收到多个消息,粘包 应…

计算两个日期相差几年几月几天,考虑闰年平年

java8以下 计算两个日期相差几年几月几天,考虑闰年平年 // java 计算两个日期相差几年几月几天,考虑闰年平年public void calculateDifference(String startDade, String endDate) {Calendar calendar1 Calendar.getInstance(); // 第一个日期&#…

vue+Nodejs+Koa搭建前后端系统(八)-- vue router路由嵌套、stroe状态管理

前言 本文是在该系列的基础上,针对前端的修改。其中前端采用vue3框架,脚手架为vite,ui组件库为ElementPlus,路由为vue-router,状态管理库为Pinia。 路由嵌套 整合模块数据文件(路由、菜单) …

HTB- Sau

HTB- Sau 信息收集立足root 信息收集 访问55555端口,得到Request Baskets。 最底下发现将详细版本暴露了出来。 这有一篇可能的ssrf漏洞文章(这个图是postman)。 不过似乎没有明白使用方法。在最上面还有几个功能没有尝试过。 在Settings上…

k8s之Helm安装

一、最快安装–官网提供的脚本–默认获取最新版本 cd /usr/local/src/ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.shhelm search hub wordpresssource <(helm completion bash) h…

思迈特CEO吴华夫:大模型加持下的ABI平台,彻底解决指标平台与BI的割裂之痛丨数据猿专访...

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在现代商业环境中&#xff0c;企业的业务需求日趋复杂&#xff0c;对数据分析的依赖也越来越深。从实时销售情况到市场趋势分析&#xff0c;从客户行为研究到产品优化调整&#xff0c;每一个环节都离不开数据的支撑。然而&a…

vue3.0 bpmn-js + TS 简易教程

前言 bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 这里主要记录本人在开发bpmn中的流程 参照了 LinDaiDai_霖呆呆 大佬的文档 实战 安装 bpmn npm install --save bpmn-jsHTML <template><div class"designer-container&quo…

【深度学习】Collage Diffusion,拼接扩散,论文,实战

论文&#xff1a;https://arxiv.org/abs/2303.00262 代码&#xff1a;https://github.com/VSAnimator/collage-diffusion 文章目录 AbstractIntroductionProblem Definition and Goals论文其他内容实战 Abstract 基于文本条件的扩散模型能够生成高质量、多样化的图像。然而&a…

WEB集群——tomcat

1. 简述静态网页和动态网页的区别。 2. 简述 Webl.0 和 Web2.0 的区别。 3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。 一、简述静态网页和动态网页的区别 &#xff08;1&#xff09;静态网页 1.什么是静态网页 请求响应信息&#xff0c;发…

解码产品经理:深度剖析这个角色的五大职责

了解产品经理的角色 在一个组织中&#xff0c;产品经理的角色是至关重要的。他们不仅需要理解市场动态和用户需求&#xff0c;还需要将这些信息转化为实际的产品策略和行动计划。产品经理在团队中的地位是核心的&#xff0c;他们需要协调各个部门&#xff0c;包括开发、设计、…

可缝合神经网络

文章目录 Stitchable Neural Networks摘要本文方法实验结果 Stitchable Neural Networks 摘要 包含大量强大的预训练模型族(如ResNet/DeiT)的model zoo已经达到了前所未有的范围&#xff0c;这对深度学习的成功有重要贡献。由于每个模型族都由具有不同尺度的预训练模型(例如&…