theme: channing-cyan
前言
大家好,继 Flutter跨进程混合栈渲染的实践——子进程WebView 之后,利用业余时间对整个插件进行了完善和基础测试,诚然距离投入生产还有一段距离,但总算能达到beta
阶段了(仓库地址在文尾)。
这个插件断断续续开发了有小半年,最初只是为了隔离webview
,但随着不断地实践,发现在app容器化
、小程序
等方面也可能有不错的应用和表现。故,在此做一个记录和分享,希望能帮到有需要的朋友,同时对于不足的地方,也望大佬能给予指点。
限于篇幅,此文仅对插件做一个整体性的介绍,后续会对具体的子模块做详细介绍。
介绍
目前仅支持android端,以下也基于Android平台进行介绍
flutter_hybird_webview
旨在将webview隔离到子进程以避免其对主进程的影响,同时为了不增加栈的管理复杂度
,采取子进程渲染,主进程显示的方式来保持flutter栈
的唯一性。
演示(多图)
gif已转为webp。总计约10m+
技术原理
组织架构图
老版架构图,结构一致,细节略有变动。
原理介绍
下面对插件的主要模块进行介绍
渲染模块
flutter端采用的是texture widget
,其特点是可以配合平台端的surface
进行显示。
当我们在flutter侧通过widget
构建一个页面时,最终会在engine
侧形成一个layer tree
。skia
会通过里面录制的绘制指令,绘制(skia底层绘制方式还是由平台来决定的,如opengl,vulkan,metal等)到surface
所指向的内存缓冲区。
rasterizer runner为engine在app进程开启的一条线程,主要负责绘制相关。
还有诸如:platform、io、ui等Runner.
如果了解绘制原理,应该知道surface是由平台侧提供的,而平台侧的surface
对应Surface Flinger
服务进程的layer
(一一对应关系),其内部含有一个BufferQueue
可以根据需要来申请一块内存缓冲区并通过映射共享给其他进程(如app)。简单讲,surface内部包含一个指针并指向了一块内存区域,我们绘制的数据最终会在那个内存区域里。
这里不做过多介绍,有兴趣的可以百度
由上面我们可以知道surface
是具有跨进程能力的,那么我们便可以通过在主进程创建surface
然后共享到子进程(甚至可以反过来),并由其来提供绘制指令。
通过上述方式便确保了栈的唯一性,降低了管理栈所带来的额外成本。
Touch event模块
Touch event由硬件传感器生成并最终传递到app,Input event则由输入进程(包括那个软键盘窗口)
产生并传递到app,也就是说这些event 天然具备跨进程传递。
Flutter的触摸事件是由平台侧传递过来,并进行分发的,可参考Flutter——原生View的Touch事件分发流程 以及更早的文章。
起初我是打算在平台侧做拦截并进行分发的,但考虑到view
在具体使用时的环境可能非常复杂,如 部分显示
、表面有浮层
,可见但无法消费事件
等等,所以放弃了这个方案,并参考google的实现,改由在flutter侧处理并回传到平台层,再分发到子进程。
这里之所以增加event coordinator
模块,是因为view在具体显示的时候并非充满整个页面,需要将点击坐标转换成相对坐标分发到子进程的remote view
。
Input event模块
由于软键盘的拉起,需要view获得focus
以及display id
的匹配,官方的trick
并不能解决在多进程下无法拉起软键盘的问题。
经过思考得出两个方案:
方案一
通过对源码的分析,可以知道app
是通过binder
与IMS
通信来实现输入功能的,那么我们对IMS的binder : IInputMethodManager
进行代理以监听软键盘的show
请求,然后通过app的binder
通知主进程代劳,并将input event
分发到子进程,便可以完成这个功能。
一通理论分析后,便信心满满的开干,结果被现实打得鼻青脸肿…Android较新的版本,不仅binder
有缓存,就连imm
也增加了缓存,而且由于高版本的api限制
导致无法正常hook
。在通过进一步研究和大佬的指点,发现可以通过native层的hook或者匿名调用
来绕过限制。
由于工程量较大,所以插件的实现暂未采用方案一
,而是临时采取方案二
。
方案二
在仔细分析了android和chromium的input模块
的通信流程和源码后,采用了通过对getSystemService
方法重写,然后对调用堆栈进行分析,来判断webview
是否请求了软键盘的弹出操作。
具体运行效果尚可,但个人的目标还是方案一
,后续将会抓紧时间来实现此方案。
通信模块
整个插件按照通信端来划分,可以分为3部分:
flutter端
平台端
子进程端
插件的管理及widget/view
的内部通信都是基于这个通信架构来设计开发的,通信实现由channel
和binder
配合完成。为了降低通信管道的复杂度,对通道进行了属性上的划分:管理通道
和私有通道
,
管理通道: 负责整个插件的生命周期管理及维护命令的分发。
私有通道:view内部的私有通道,处理定制化命令的分发。
具体通信流程如下图:
后语
至此整个插件的基本介绍就到此结束了,由于一些子模块涉及的东西较多,所以会在后续文章中做详细介绍,谢谢大家阅读,如有错误还望指出。
仓库地址
暂时不能用于生产哦~ 具体分支含义见仓库的readme。
其他文章
Flutter在Android平台上启动时,Native层做了什么?