Flutter 混合架构方案探索

得益于 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

条件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.rhkb.cn/news/91222.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bitcoin 加速交易操作示例

这里以 Bitcoin Ordinals NFT 为例, 进行加速交易演示 第1步:新建子账户 温馨提示:如果有多条鱼未确认,也只需1个账户即可,不必搞多个子账户 第2步:切换回到老地址(Account 1) 第3步…

【Unity每日一记】进行发射,位置相关的方法总结

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基本介绍登场角色应用场景 案例实现案例一类图实现 案例二:借贷平台源码剖析传统方式实现分析状态修改流程类图实现 案例三:金库警报系统系统的运行逻辑伪代码传统实现方式使用状态模式 类图实现分析问题问题一问题二 总结文章说明…

基于飞桨图学习框架实现的城市地点动态关系挖掘

李双利 飞桨开发者技术专家(PPDE),百度研究院商业智能实验室研究实习生,中国科学技术大学在读博士生。 主要进行时空数据挖掘和图深度学习的相关研究工作。曾获2021年百度研究院年度优秀实习生,有多篇基于飞桨完成的…

error_Network Error

此页面为订单列表,是混合开发(页面嵌入在客户端中) 此页面为订单列表,此需求在开发时后端先将代码发布在测试环境,我在本地调试时调用的后端接口进行联调没有任何问题。 此后我将代码发布在测试环境,在app中打开页面&#xff0c…

smardaten实战丨谁说无代码不能开发出漂亮的门户首页?

一、需求背景 门户首页对于一个公司或组织来说是一个极其重要的网站页面,它可以作为访问者了解和获取相关信息的入口,同时也是展示品牌形象和吸引目标受众的重要工具。 开发一个门户首页需要开发团队在向访问者展示关于公司或组织基本信息的基础上&…

Qt自定义对话框

介绍 自定义框主要通过对现有对话框QDialog类的派生,根据需求编写成员函数、重载信号函数、槽函数,进而实现在主QWidget中点击某个按钮后,一个对话框的弹出 流程 简化创建派生类 最后点击完成即可。 自定义ui界面,编写成员函数…

Netty:用ByteBufUtil的函数将字节数组、或者ByteBuf的内容转换为十六进制表示的字符串

用ByteBufUtil的hexDump(byte[] array)函数将字节数组的内容转换为十六进制表示的字符串 package com.thb;import io.netty.buffer.ByteBufUtil;public class Demo {public static void main(String[] args) {byte[] b new byte[] {0x68, 0x16, 0x03, 0x04, (byte)0xae};Stri…

ASR 语音识别接口封装和分析

这个文档主要是介绍一下我自己封装了 6 家厂商的短语音识别和实时流语音识别接口的一个包,以及对这些接口的一个对比。分别是,阿里,快商通,百度,腾讯,科大,字节。 zxmfke/asrfactory (github.c…

前后端分离------后端创建笔记(07)表单验证

1、我输入数据,然后关闭,重新打开会发现残存的数据仍然保留着 2、点了这个x号,数据就全部被清理了 3、点这三个地方,数据全部都清理掉 4、这里先写一个方法 4.1 定义一个方法 4.2 这里表单的数据在哪里,就是这个 4.3 …

Qt扫盲-QWidget理论使用总结

QWidget理论使用总结 一、概述二、顶层 控件 和子 控件三、复合控件四、自定义控件和绘制五、大小提示和大小策略六、事件七、一组函数和属性八、QWidget样式表九、透明度和双缓冲十、创建半透明窗口 一、概述 widget 是用户界面的最小单位:它从window系统接收鼠标…

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材 下载地址:https://download.csdn.net/download/Highning0007/88204017

Jmeter-压测时接口按照顺序执行-临界部分控制器

文章目录 临界部分控制器存在问题 临界部分控制器 在进行压力测试时,需要按照顺序进行压测,比如按照接口1、接口2、接口3、接口4 进行执行 查询结果是很混乱的,如果请求次数少,可能会按照顺序执行,但是随着次数增加&a…

《零基础实践深度学习》(第2版)学习笔记,(二)机器学习和深度学习综述

文章目录 1. 人工智能、机器学习、深度学习的关系2. 机器学习2.1 实现原理2.2 如何实施 3. 深度学习神经网络核心概念 1. 人工智能、机器学习、深度学习的关系 **人工智能(Artificial Intelligence,AI)**是研发用于模拟、延伸和扩展人的智能…

helm安装harbor + nerdctl 制作push 镜像

参考 文章:Helm部署Harbor_helm harbor_风向决定发型丶的博客-CSDN博客 安装好后使用 nerd containerd对接harbor_containerd 容器 insecure-registries 配置_柠是柠檬的檬的博客-CSDN博客 推送镜像 Containerd 对接私有镜像仓库 Harbor - 知乎 接下来我们来…

“一日之际在于晨”,欢迎莅临WAVE SUMMIT上午场:Arm 虚拟硬件早餐交流会

8月16日,盛夏的北京将迎来第九届WAVE SUMMIT深度学习开发者大会。在峰会主论坛正式开启前,让我们先用一份精美的元气早餐,和一场“Arm虚拟硬件交流会”,唤醒各位开发小伙伴的开发魂! 8月16日,WAVE SUMMIT大…

【注解使用】使用@Autowired后提示:Field injection is not recommended(Spring团队不推荐使用Field注入)

问题发生场景: 在使用 IDEA 开发 SpringBoot 项目时,在 Controller 类中使用注解 Autowired 注入一个依赖出现了警告提示,查看其他使用该注解的地方同样出现了警告提示。这是怎么回事?由于先去使用了SpringBoot并没有对Spring进行…

三、性能测试场景设计

性能测试场景设计 一、引言:如果公司要求你去做性能测试,遇到这些场景,我们要如何设计?二、6种常见设计方法1、普通性能场景设计2、负载测试性能场景3、压力测试场景4、面向目标性能场景 一、引言:如果公司要求你去做性…

W6100-EVB-PICO 做UDP Server进行数据回环测试(七)

前言 前面我们用W6100-EVB-PICO 开发板在TCP Client和TCP Server模式下,分别进行数据回环测试,本章我们将用开发板在UDP Server模式下进行数据回环测试。 UDP是什么?什么是UDP Server?能干什么? UDP (User Dataqram P…

海量数据迁移,亚马逊云科技云数据库服务为大库治理提供新思路

1.背景 目前,文档型数据库由于灵活的schema和接近关系型数据库的访问特点,被广泛应用,尤其是游戏、互联网金融等行业的客户使用MongoDB构建了大量应用程序,比如游戏客户用来处理玩家的属性信息;又如股票APP用来存储与时…