找回密码
 会员注册
查看: 22|回复: 0

FlutterEngine在Pad上的演变

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
67441
发表于 2024-10-9 14:51:20 | 显示全部楼层 |阅读模式
FlutterEngine在Pad上的演变 FlutterEngine在Pad上的演变 张冠南 贝壳产品技术 贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容 2021年11月19日 19:32 01背景贝壳在iPad和安卓平板(这两种后续统称为PAD)上面进行了多款产品的开发;为了提高开发效率,实现一套UI跨端展示,贝壳使用Flutter进行了大量实践。在手机上屏幕比较小,一般一个页面就是一个完整的Flutter页面,这种场景使用一个FlutterActivity容器进行加载就可以了。然而在PAD上,屏幕比较大,交互更加的丰富;由于业务上大量的用到了地图和一些H5页面,会出现同屏Native和Flutter混合的情况,会使用到FlutterFragment和Fragment在同屏切换的场景;因此我们不得不采用Native+Flutter的混合方案,FlutterEngine作为Native和Flutter桥连的容器在这中间扮演着重要的角色。这期间贝壳PAD经历了单FlutterEngine到多FlutterEngine再到Flutter2.0的FlutterEngineGroup这一系列的容器改变,下面会详细的介绍FlutterEngine在PAD上的演变过程。02FlutterEngine简介1、FlutterEngine是什么?A single Flutter execution environment.通过FlutterEngine类的第一行注释,我们可以看出大概意思是FlutterEngine是一个独立的最小的Flutter运行环境。FlutterEngine位于:(这里以Android端源码为例)io/flutter/embedding/engine目录下面,阅读源码后这里总结了FlutterEngine的一些特点:(1)FlutterEngine是dart代码能够运行在Android应用程序的一种容器。(2)一个APP中可以存在多个FlutterEngine来执行dart代码,多个FlutterEngine的内存数据是相互隔离的,只能通过Isolate ports来进行通信;FlutterEngine可以在后台运行dart代码,也可以通过FlutterRenderer搭配Flutter framework将UI渲染在屏幕上面;运行Dart代码,需要获取FlutterEngine的DartExecutor的实例,然后执行executeDartEntrypoint(DartExecutor.DartEntrypoint)方法,需要注意的是这个方法不能被调用两次。(3)当第一个FlutterEngine初始化的时候,会去加载FlutterEngine所需的一些Native so库和开启Dart VM,后续的其他FlutterEngine运行的时候会使用同一个Dart虚拟机,不会重复创建。2、为什么需要FlutterEngine?通过第1部分了解到了FlutterEngine的一些基本特性,那么FlutterEngine具体起到了什么样的作用在Android上通过继承一个系统的FlutterActivity来显示Flutter的UI,FlutterActivity内部使用FlutterView,而FlutterView持有FlutterEngine的FlutterRenderer将FlutterUI通过FlutterTextureView或者FlutterSurfaceView渲染出来,FlutterView会在FlutterActivity启动的时候(onStart的时候)去绑定FlutterEngine,FlutterEngine在创建的时候会实例化一些重要的类:FlutterLoaderFlutterJNIDartExecutor其它主要用来查找flutter的资源和加载一些flutter相关的so库定义了Java与C/C++库之间互调函数接口函数将FlutterJNI和AssetManager的实例传递给DartExecutor的构造创建一个DartExecutor的实例对象,用于实例化各种系统的channel用于Flutter和Native侧进行通信FlutterEngine的构造中除了上述几个关键的类之外还有就是进行一些列的系统channel的实例化,用于Dart端和Native端的通信;还有就是自动注册yaml文件中依赖的各种pluginFlutterEngine在这其中扮演的角色如下图所示:通过FlutterRenderer将Flutter的UI渲染到屏幕,Native侧的系统平台事件向Flutter层的传递,以及Flutter层渲染树的变化通知到Native,这些操作都是通过FlutterEngine来完成的,因此FlutterEngine在Native和Flutter混合方案中扮演着很重要的角色。03贝壳PAD上FlutterEngine的演变过程贝壳PAD从引入Flutter开发到现在,针对引擎的使用方案,可大致分为三个阶段:1、单FlutterEngine在贝壳PAD项目初期,最早考虑的是使用单个FlutterEngine的方案,就是在Flutter产物初始化的时候,去初始化一个FlutterEngine然后缓存到内存,后面打开的所有的页面都去和这个缓存的FlutterEngine去绑定使用。如果打开的都是全屏的Flutter页面,这种方案是没有问题的。但是在PAD的复杂交互中存在这样一种情况:同一个页面使用Activity嵌套原生的Fragment页面和多个FlutterFragment的时候(如下图)。这是一个典型的多Tab联动右侧页面切换的业务场景,Tab1为Native的Fragment,Tab2和Tab3是Flutter页面,像这种场景,如果Tab2和Tab3的Fragment容器使用同一个FlutterEngine的时候就会出现闪屏的问题。闪屏现场如下:闪屏的原因:Tab2和Tab3的flutter页面使用的是同一个FlutterEngine去绑定。当它们之间发生切换的时候,会先执行FlutterView的detachFromFlutterEngine()方法将当前FlutterFragment的FlutterView和FlutterEngine解绑;然后去和另外一个FlutterFragment的FlutterView进行重新绑定,重新绑定是需要一定的时间的,所以会在下一个Flutter页面真正显示出来之前先显示之前绑定的页面,然后下一个FlutterView真正绑定建立连接之后会重新刷新视图发生变化,这就造成了Tab切换视图的闪屏问题。在这样的场景下,我们发现了单个FlutterEngine不好的地方,需要我们控制好引擎的绑定和解绑等一系列的操作,单个Tab页面无法完全独享FlutterEngine,而谷歌在提到FlutterEngine的时候也是希望在一个FlutterEngine上的业务是一个完整的功能模块,因此我们探索了多FlutterEngine的方案。2、贝壳PAD多引擎复用方案贝壳PAD多引擎的思路大概是这样:在Flutter产物初始化的时候,会提前初始化预加载几个FlutterEngine并将其缓存在内存当中,在页面显示的时候选择不同的FlutterEngine去加载Flutter页面,这样就避免了多FlutterFragment复用同一个FlutterEngine带来的闪屏问题。多引擎完全的隔离了业务,解决了复用带来的闪屏问题,但是采用多引擎也是有自己的弊端,比如:(1)内存占用高:在Flutter2.0之前,新增一个FlutterEngine大概会占用20M左右的内存,所以多个引擎对内存占用较高,这里也是牺牲内存解决体验问题,不过好在Flutter2.0对多引擎占用内存问题进行了优化,后面会详细介绍。(2)FlutterEngine间通信问题:使用多引擎去加载页面,如果这几个页面之间有一些交互,就会带来多引擎之间的通信问题。Native向多个引擎加载的Flutter页面发送通知进行页面数据同步:或者一个Flutter页面向另外一个FlutterEngine加载的Flutter页面进行数据同步:在以上场景下就会存在通知无法收到的问题,因此我们通过自研通信Plugin去解决多引擎之间的通信问题。多引擎通信Plugin在上述FlutterEngine简介那里介绍过,每一个FlutterEngine使用的时候都会自动注册yaml中的plugin,当plugin插件注册到引擎之后,会回调plugin的onAttachedToEngine方法。多引擎通信Plugin的原理是:(1)图中步骤2:每一个FlutterEngine初始化的时候,会将步骤1中的通信Plugin在FlutterEngine上注册一次。(2)图中步骤3:通信Plugin在每一个FlutterEngine注册的时候会在Plugin的onAttachedToEngine方法当中将通信插件的EventMethodChannel对象进行缓存,多引擎就会缓存多个EventMethodChannel对象(用来Native侧和Flutter侧的通信)(3)图中步骤4:当需要从Native向多个FlutterEngine的Flutter页面发送通知的时候,只需要获取所有缓存的EventMethodChannel对象,然后进行消息发送即可。Flutter侧向Native侧在多引擎下不会存在问题,是由于每一个Flutter页面都是绑定在唯一一个FlutterEngine上,所以只需在Flutter代码中向相应的引擎发送消息即可。解决多FlutterEngine的关键就是缓存通信插件在每一个FlutterEngine下的EventMethodChannel对象;其次就是借助Native进行消息中转。3、贝壳Flutter2.0多引擎方案在Flutter2.0之前使用多FlutterEngine方案,会对内存有很大的消耗。Flutter2.0引入了FlutterEngineGroup,FlutterEngineGroup 方案使用了多 Engine 混合模式,官方宣称除了一个 Engine 对象之外,后续每个 Engine 对象在 Android 和 iOS 上仅占用 180kb。在项目中实际测试,每增加一个FlutterEngine内存会有所增加,页面销毁之后FlutterEngine也被回收掉。下面详细的看一下FlutterEngineGroup的源码:FlutterEngineGroup类中除了构造函数,有一个比较关键的方法createAndRunEngine(@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint);下面代码注解当中有这个方法的详细说明。在FlutterEngineGroup中创建FlutterEngine,第一次创建会将创建的FlutterEngine对象放到缓存,之后如果再次创建FlutterEngine,会基于已经创建的FlutterEngine去创建,底层会复用 GPU 上下文, font metrics 和 isolate group snapshot等,节省了内存空间。基于FlutterEngineGroup这样的特点,贝壳PAD在使用Flutter2.0的FlutterEngine的方案是:在Flutter产物初始化的时候,通过FlutterEngineGroup创建一个FlutterEngine使其存储在缓存当中,作为常驻引擎,这样减少了FlutterEngine初始化所消耗的时间。后续每打开一个页面都会通过FlutterEngineGroup去基于常驻引擎去创建FlutterEngine。FlutterEngineGroup方案和多引擎方案对比:04总结目前贝壳PAD上面已经进行了Flutter2.0的适配,后续会继续探索FlutterEngineGroup如何做到节省内存空间的深入分析以及Flutter方面的性能研究。贝壳在PAD上实践Flutter的过程当中,踩过很多的坑,也总结很多的经验;在面临困境的时候,大家一起努力制定出自己的一套可行性方案解决行业痛点,本篇文章是我们在使用FlutterEngine过程当中踩过的坑总结出来的经验,分享给大家,共同进步! 预览时标签不可点 大前端69Flutter15移动端37大前端 · 目录#大前端上一篇iOS端循环引用检测实战下一篇Flutter Navigator局部页面切换实践关闭更多小程序广告搜索「undefined」网络结果
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-3 01:16 , Processed in 0.515477 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表