ICBU建设Flutter的核心目标是保障Flutter的性能和质量,进一步提升提效效果,进一步扩大提效范围。本次分享将由ICBU Flutter架构师路少德为大家详细介绍ICBU在Flutter实践中的思考和沉淀。整体分为两部分,第一部分通过业务背景和技术原理推导出接入Flutter的必要性和待解决的问题。第二部分以接入工作中的混合工程和混合栈为重点进行技术上的阐述。
演讲嘉宾简介:路少德,花名白及,ICBU Flutter架构师,将Flutter接入ICBU,设计实现ICBU的Flutter架构和基础组件,实现和输出ICBU的混合栈、多语言等能力,并推动ICBU无线技术部持续朝Flutter化演进。
以下内容根据演讲视频以及PPT整理而成。
观看回放http://mudu.tv/watch/5817421
本次分享主要围绕以下四个方面:
一、背景介绍 二、Flutter技术接入 三、Flutter能力补齐 四、结果&规划
一、背景介绍
ICBU Flutter现状
首先在场景覆盖层面,ICBU目前有30+Flutter页面,交易业务覆盖90%,目前所有新页面都选择Flutter进行开发。在人员层面,所有无线开发掌握Flutter开发知识,Android&iOS间的壁垒正在逐渐消失。在提效效果层面,目前已累计提效大量人日,同时获得了不错的技术成果,如参与AliFlutter的建设,并提出了基于官方的工程体系和混合栈方案,及多语言、本地化等多种国际化方案。
下图是APP使用Flutter后的页面,包括Android版的会话列表、iOS的会话列表、RTL(阿拉伯语)的会话列表,以及购物车等交互非常复杂的页面。
为什么选择Flutter
Alibaba.com属于B类国际电商业务,B类国际电商业务在当下的特点是发展非常迅速,业务压力很大,每次的业务需求都会超出人员需求,并且开发资源会被其他事情占用(如技术架构演化),此时不得不进行非关键业务的取舍或者加班加点赶工期。但这两种策略都不能从根本上解决问题,ICBU希望的是通过提高研发效率,用更少的开发资源承担更多的业务需求,甚至可以解放生产力做其他事情。第二个特点是业务非常复杂,下图右侧展示了如果需要进行跨境B类贸易,则需要经过的28步流程。在如此复杂的业务场景下,由于Android和iOS开发对业务的理解的不同,导致业务实现出现差异,从而不得不耗费更多的开发资源进行对齐,所以ICBU希望有跨端一致性的框架解决这个问题。
再进一步而言,ICBU无线技术诉求是有一个取代Native开发的跨端框架。对于Native大家已经非常熟悉,它的特点包括极致体验,优秀性能,同时具备高稳定性。在此之前,ICBU已经尝试过很多跨端框架,包括H5、小程序、uni-app、Weex、以及阿里集团内部的DinamicX等动态组件,上述框架都有各自的优点和使用场景,但是各自都存在一些问题,如性能差、一致性差、存在能力缺陷、开发效率低等,都无法取代Native开发。
在18年左右,Flutter进入了大家的视线中,下图右侧展示了Weex、Native与Flutter技术原理的对比,以Android开发为例,Native布局的渲染就是将Layout XML变成了Native View Tree,Weex也是借助了类似的原理,即上层使用JS,在渲染时经过几次DOM的转换,最终以Native View Tree进行页面的渲染。因为Weex相比与Native多一层JS,所以性能上无法和Native一样,并且随着页面复杂度越来越高,与Native的性能差距也会越来越大。而Flutter运行在Dart虚拟机上,直接进行绘制,没有借助Native View Tree。通过技术原理,我们可以推断Flutter具备完美一致性,另外就是性能上限比较高。
二、Flutter技术接入
既然决定采用Flutter,就需要弄清楚我们要解决什么问题,以及解决的方案。下图展示了Flutter与Android的比对,并且划分了四个技术体系,分别包括技术环境,工程体系,能力&效率,以及稳定性。而且分别列出来Android和Flutter当时的状态,通过一一对比,就可以发现每一项问题及对应的解决方案,再将解决方案重新规划。如首先解决接入问题,即混合工程和混合栈问题;再针对Flutter薄弱点进行能力补齐,即视频播放和数据预加载能力;接下来是解决Flutter动态化能力;最后在前面的解决方案都顺利的情况下,利用Flutter跨端提效的特点,在更多应用上进行拓盘,如应用于卖家App或者PC上。
Flutter-混合工程
Flutter开发大体分为两种,一种是原生开发,另一种是混合开发。原生开发适合开发新App,混合开发更适合存量App,有基建成本,但官方支持不够成熟。混合开发核心需求解决两个问题,即混合工程和混合栈。混合技术首先需要解决前三个问题:
- Native工程如何源码依赖Flutter工程;
- Flutter工程如何生成中间产物(如Android上是aar,iOS上是Framework等);
- Native工程如何依赖中间产物。
右侧是以AliFlutter为代表的工程结构,下层是AliFlutter基建,原则是将AliFlutter的基建都以产物的形式交付给业务团队。业务团队在上层只需要关心三个工程:Native主工程、Flutter主工程、Flutter业务工程。
打包
混合工程第二个问题是打包。下图左侧是在源码依赖情况下,工程结构的示意图。右侧是在产物依赖情况下,工程结构示意图。黄色部分是Native代码实现,蓝色部分是Flutter代码实现。从左至右,Native部分代码没有很大变化,而Flutter代码发生了翻天覆地的变化。所有Flutter代码都被打包一个产物,而Flutter插件所使用的Android代码或iOS代码被分门别类的打成独立的包。如果再细分,可以变成三个问题,首先是如何区分release和debug产物;其次是如何递归的打出插件的Native产物(图中黄色部分);最后是如何一键依赖如此多的产物。通过Shell脚本打出release和debug产物,再通过Gradle脚本打包Flutter产物,再通过Gradle脚本注入的方式将每个插件的信息注入到每个插件的打包脚本中,如此便可以打出每个产物,且每个产物都配有POM文件,通过POM文件可以实现一键递归依赖。
Flutter-混合栈
混合栈分为两个问题,首先是引擎复用方案,其次是Native页面及Flutter页面的约束方案。下图左侧是不复用引擎和复用引擎的区别。复用引擎无需在每个页面生成独立的引擎,从而减少引擎内存的占用,减少引擎创建时间,提高内存稳定性,并且减少页面启动时间。
复用引擎需要解决的核心问题是生命周期的处理,也就是Native生命周期、Flutter引擎的绑定和解绑、及Flutter自己的生命周期这三部分之间的关系和处理非常关键。
最简单的例子,当页面从打开到关闭,是一个线性的流程,故可以与Flutter的生命周期进行绑定。第二点,从A页面打开半透明B页面时,Android不会调用A的onStop方法,如果在onStop方法上做一些特殊处理,则不会被调用,出现问题。第三点,如果从B页面返回A页面时,会先调B的onPause方法、再调A的onStart、onResume、再调B的onStop、onDetach,此时如果在onResume中做一些正向操作,同时在onDetach做负向操作,就会出现问题。最后ViewPager和Transaction的Fragment生命周期管理问题非常令人头疼,因为ViewPager和Transaction对生命周期的处理都不同,而且会调用自己独特的方法,对生命周期的处理进一步增加难度。
综上,Android的生命周期十分复杂,牵一发动全身,同时Flutter引擎处理也十分复杂,如果绑定和解绑时序错乱会带来非常严重的后果。
引擎复用-技术方案
下图梳理了将Flutter引擎与Android生命周期间的绑定和解绑的原理,如引擎绑定和解绑分别放在onResume和onPause上,从而避免半透明界面问题,同时又将Flutter生命周期也放在其中。针对返回页面问题,加上了延时和轮询队列保证onResume中的方法晚于上一个页面销毁的方法,突破生命周期的限制。
下图通过代码对比了三种不同的方案,第一个是官方的Flutter SDK,第二个是闲鱼的Flutter Boost,最后是ICBU混合栈。可以发现官方的Flutter SDK直接调用了onStart方法,并没有解决半透明页面问题。同时在B页面返回A页面时,使用了Handler().post()的消息队列,造成一定程度上的时延。Flutter Boost对生命周期做了很多梳理,但是也做了一些简化,如onPause时没有调用Flutter的postPaused方法,这样可以极大的简化生命周期的复杂度,梳理出所需要的生命周期。而ICBU的混合栈则是兼顾了Native和Flutter的生命周期。
上层页面约束
上层页面约束最核心的是要保证一个Native页面要一比一的对应Flutter页面。ICBU的页面约束是利用了Native的页面栈及Flutter的页面栈,通过两个栈一起出一起进的行为保证一比一的页面约束。但其实页面约束层还是存在几点问题,首先是稳定性低,引擎复用一旦出错会导致约束出错,其实是适用场景有限,不支持多Flutter Tab。与之对应,Flutter Boost的页面约束层非常优秀,它通过对每一个Flutter容器单开一个Navigator,来保证页面约束是一比一的。因此ICBU计划接入Flutter Boost,共建Flutter Boost。
阶段性总结
ICBU混合工程实现了一键切换源码和产物依赖,实现了一键本地或MTL打包,搭建了AliFlutter初版工程框架。在混合栈方面,主要是在内存和页面启动时长方面的提升,复用引擎相比于不复用引擎在内存上有所减少,下图中只是对比了两个页面,随着打开页面的增加,内存差距也会越来越大。同时复用引擎节省了大概300毫秒左右的启动时长。在经历了半年的初步建设之后,Flutter在提效,UI丰富力及极致体验方面都表现优秀,当然在视频播放、动态化能力、架构优化等方面还有待优化,进一步提升研发效率。
三、Flutter能力补齐
视频播放解决方案
一般的视频方案是直接提供View,然后直接播放。但在Flutter中很难复用View,即便可以,代价也非常昂贵。ICBU借鉴Flutter官方和闲鱼的代码,整个Flutter视频渲染流程如下图右侧,我们通过纹理传递视频实现跨技术栈的渲染,Native视频播放器将自己的视频流通过纹理渲染到Flutter的控件上。对视频的播放暂停等控制行为通过Flutter自带的消息通道进行传递。如此便可以封装成一个完整的Flutter的播放组件,同时底层可以复用App中已有的视频播放。
视频技术方案如下图,底层是播放器,通过纹理的传递向Flutter提供视频播放的能力,播放控制和优化策略是通过统一视频开放接口向上支持。Flutter自己可以在上层可以封装各种组件,进行业务提效。Flutter视频页启动时长相较于Weex降低了400ms,对应视频方案已输出给考拉,健康等用户。
Flutter动态化
大家都知道Flutter的动态化是它的薄弱点,下图展示了业界所有的动态化技术方案在各个方面的比对情况。首先一类是编译产物下发,如Hot patch;第二类是JS类框架,如RN,Weex,H5;第三类是以淘系为代表的动态组件框架,如DinamicX。经过反复的考量,编译产物下发的政策风险较高,Google Play和AppStore都不允许此方案的上线,而JS类框架性能损失很大,而以DinamicX为代码的动态组件方案在各个方面都取得了优秀的平衡。DinamicX是组件级接入,使用非常灵活,而且它使用了Android Layout Xml,学习成本更低。其配合Flutter可以达到1+1>2的效果,借助Flutter的一致性,不需要再维护Android和iOS两套SDK,维护成本有所减少,针对已使用DinamicX的业务,其迁移成本更低。
四、结果&规划
ICBU的Flutter建设工作在前期参与了同闲鱼的技术共建,实现了多种控件,具备了国际化RTL能力,适配了高版本的SDK。在未来,我们期望所有Native页面将被Flutter页面取代,针对动态化场景使用DinamixX,若还是有欠缺,则使用H5,Weex,小程序等进行补全。
架构图
通过左侧工程体系的建设,保证研发效率,同时针对不同的中间件进行建设,以支持不同的业务场景,在未来,除了Android和iOS,我们还希望Flutter可以带来更多平台上的优势。
结果
从结果层面,我们沉淀了丰富的技术,包括混合工程体系,中间件等。同时通过与阿里巴巴集团的紧密合作,向AliFlutter提供了工程体系及多语言能力,同闲鱼一起共建了Dx-Flutter。使用Flutter之后,开发效率相比于使用Native提升了60%以上。通过Flutter,使得资源弹性增大,避免出现Android和iOS资源不平衡导致的业务不一致的问题。最后,通过Flutter节省了大量人日,让更多的人力投入到端智能和数据化及更多其他业务中。技术影响力也在逐渐扩大,目前很多的技术方案已提供阿里集团内部很多BU。
未来规划
ICBU建设Flutter的核心目标是保障Flutter的性能和质量,进一步提升提效效果,进一步扩大提效范围。拆分开来分别对应性能&质量,接入及拓盘。在早期,Flutter本身不够成熟,各个问题的最优解都不是很清楚。在现在,Flutter逐渐成熟,大家对各部分的最优解都达成了一致,阿里希望有个统一的Flutter中台,承担大部分的基建工作。因此,ICBU也计划进一步接入AliFlutter,减少维护成本,同时对AliFlutter形成反馈,增加Flutter基建的价值。
关注「淘系技术」微信公众号,一个有温度有内容的技术社区~