Flutter跨进程混合栈渲染的实践-子进程WebView

1,873 阅读5分钟

  # 分享作者


昵称: 吉哈达

前言

首先祝大家中秋节快乐,而明天又要上班啦~ 哈哈哈。不过,立此之处,国庆可期矣~

好了,书归正传,在此我想分享一下关于我在Flutter 安卓端跨进程渲染所做的一些实践。

起因

随着项目不断的迭代,功能日益复杂,内存占用也与日俱增。在压测过程中,app的崩溃也多是因为各种原因的内存泄漏异常抖动并最终引发OOM而被系统杀死。按技术栈划分主要集中以下两端:

  1. 原生端本身的代码质量(不当设计、图片加载、对象未释放等)所造成,这点通过回溯及找到组内对应同学修复便可快速解决。

  2. 前端的代码质量(亦如上)所引起,这点则需要找到前端组的同学进行修复,但是跨组/部门的无力感我想大家或多或少都会有一些。

不管原因几何,结果都是App崩了,我们一方面找到负责的同学抓紧修复外,另一方面也在思考如何从原生解决(至少隔绝)H5导致的App崩溃问题。

分析

有一定原生开发经验的我们,便想到了子进程。而通过子进程去分担主进程的内存压力,在各大厂也均有应用,可证明它是一个比较成熟的方案,而就单进程Web-View来说,市面上也有不少成功的Android框架及技术方案的分享。

纯原生(Android)应用来讲,因为栈的统一,接入一个子进程web-view,还是比较方便的,大致开启一个子进程,然后startActivity即可,无需关心栈的管理。但是Flutter应用则分为两种栈:

1 Android栈 (管理activity)

2 Flutter栈  (管理flutter的route)

在实际应用中,H5与原生均有复杂的交互,这里不仅体现在功能上的,还包括UI上的。就算不考虑跳转动画的问题,Flutter栈内的叠加(Flutter和H5)就需要一个单独的栈管理器来处理(如Flutter Boost)。

在考虑到投入产出成本以及问题的本质并非栈管理器可以解决的情况下(如 Flutter页面部分是H5等情况),我决定用Flutter自带的Texture Widget进行H5的显示,这样统一了栈的管理,同时Texture Widget可以自由调整大小,做到任意Flutter页面的(部分)嵌入。Texture Widget需要一个Surface,而Surface又具有天然的跨进程属性这无疑大大方便了开发。

实践及结果

经过一段时间的研究和设计,最终有了一个Alpha版的框架,在此我对架构做一下简单的介绍:

flutter_remote_view_framework.png

按进程划分

主要分为两部分:

1. 主进程包含Flutter及相应的平台部分,承担surface的创建、展示、交互等的发起方。

2. 子进程主要包含zygote activity , webview 等。

进程之间通过Binder进行通信。

按流程划分

主要分为三部分:

1. Flutter侧,主要发起创建指令并最终消费子进程的渲染数据。

2. 平台侧,主要承担Flutter与子进程的web-view的通信转发功能,同时承担surface的创建功能,
   也是真正与子进程通信的模块。
   
3. 子进程,主要负责webview的创建,并使用主进程所提供的surface进行H5内容的输出。

所遇到的一些难点

系统弹窗的权限问题

在子进程中使用web-view,并渲染在指定surface 上需要借助virtual displaypresentation,但是如果presentation的创建不是基于activity context,那么则需要一个系统权限才可以正常工作,这对于我们的需求来说,是不可接受的。

为此便创建了一个Zygote activity,它工作于后台,主要责任就是提供一个context和部分presentation的创建工作。同时借助内存泄漏以尽可能长的保留它的存活时间。

交互

由于系统事件(如 触摸)是分发到当前(前台)activity stack的栈顶activity,那么当Zygote activity工作于后台的时候,我们的触摸事件是分发到了Main activity,h5则无法响应任何交互。因此我们需要在主进程做事件的分拣并通过binder转发到子进程,以此来让H5消费到属于它的事件。

触摸事件的分发及错位问题

上面的问题细分后,可以明确我们需要解决Flutter端的H5页面在非栈顶的情况下不能消费事件,因为Flutter所接受的事件由Main Activity提供,所以事件的分发也在此处处理,为此我增加了一个栈协调器(相对于栈管理要简单一些),以获取当前Flutter端的栈情况,并做出正确的分发。

经过实际实践,效果还是不错的,但也发现一个问题:点击坐标错位。经过研究发现,这主要是Flutter端布局和web view端布局不一致导致的,换言之需要计算在Flutter点击时的position相对于那个Texture widget内的相对位置,并做转换再进行分发。

通信

客观的说,这里并没有什么难点,但比较,因为操作涉及到UI,所以不仅要考虑到进程间的通信、线程切换还有各进程的主、子线程的切换。并且按领域进行划分话,又分为共有和私有通信,为此增加了communicate hub以区分各领域的通信。

结果

在一些主要问题解决后,得到了最终的效果图(debug mode):

small.gif

这个Demo并不满足生产,但是验证了它的可行性,而就真正的上线来说,还是有一部分工作要做的,如坐标转换器优化(下一个版本要做的)、协调器、垃圾回收、兜底策略等等。

到此我的分享就结束了,希望对大家有所帮助,同时也殷切希望有大佬能指出设计的不足,谢谢大家的阅读。