本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
这是 《Flutter 工程化框架选择》 系列的第四篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,可以说这是一个指引系列,所以比较适合新手同学。
本篇将着重介绍混合开发里的一些技术选型,顺便额外科普一些坑,看到后面你就会明白为什么要做这一期分类,算是相对偏技术的一期。
在 Flutter 里进行混合开发一直都是“痛点”,其中最主要的原因就是:Flutter 有自己独立的渲染引擎,因为 Flutter 控件渲染脱离了平台控件的 render tree,这也造成了 Flutter 在混合开发上的实现相对复杂了不少。
比较形象的理解:类似于把原生控件塞到 WebView 里渲染。
事实上在 Flutter 里混合开发也分为两种:在 Flutter 里混合原生平台控件(PlatformView
)和将 Flutter 混合到原生平台项目(add-to-app
),今年我们的主题是 PlatformView
,这其中又以 Android 的 PlatformView
混合原生控件“故事最多”,当然今天我们不是从头去讲解它的技术实现,对历史技术实现感兴趣的可以看之前的文章,今天主要是聊如何去“选“。
在 Flutter 里混合原生平台的控件需要通过 PlatformView
进行接入实现,在这方面 iOS 的实现方式一直变化不大,但是在 Android 上不同版本到目前就出现了三种实现逻辑,所以本篇主要介绍 Android 平台如何选择合适的 PlatformView
方案。
抛开最早期如 flutter_webview_plugin 这种直接进行覆盖原生控件的实现不谈, PlatformView
在 Android 上目前官方提供的就有三种实现方式:
VirtualDisplays
:类似于一个虚拟显示区域,将虚拟显示区域的内容渲染在一个内存Surface
上,然后同步纹理到 Engine ,事实上控件并不在渲染位置。
HybridComposition
: 通过addView
直接添加原生平台控件,需要层级覆盖时利用FlutterImageView
承载 Flutter 控件的渲染进行堆叠,原生控件会在渲染位置。
TextureLayer
:在渲染位置通过原生的透明PlatformViewWrapper
做容器,然后通过替换 Canvas 将原生控件的纹理渲染到特定 Surface 上,控件不在渲染位置,但是可以通过父容器做事件拦截。
是不是看完有点懵?不怕后面会有对应的例子解释,不理解也不要紧,这一篇是教你如何选,所以也并不需要真的理解内部是如何实现。
但是上面介绍的这三种实现也是有前提条件的,比如:
- Flutter 1.2 之前,你只能用
VirtualDisplays
- Flutter 3.0 之前,你只能用
VirtualDisplays
和HybridComposition
- Flutter 3.3 之前,你只能用
TextureLayer
和HybridComposition
- Flutter 3.3 开始,你能用
VirtualDisplays
、TextureLayer
和HybridComposition
是不是觉得有些奇怪,为什么会有奇怪的排列方式?这里面的故事线大概是这样的:
- 因为
VirtualDisplays
存在触摸事件和键盘等问题,所以通过addView
进行原生堆叠的HybridComposition
出现,解决了手势和键盘等问题 - 因为
HybridComposition
存在一些场景缺陷,比如列表卡顿,渲染线程不同步导致闪动等问题,所以VirtualDisplays
得以继续服役 - 3.0 开始默认
VirtualDisplays
和HybridComposition
被替换为TextureLayer
,但是HybridComposition
实现依旧保留,在使用时需要显式执行PlatformViewsService.initExpensiveAndroidView
才能使用,因为TextureLayer
存在没办法替代原生控件 Canvas 的场景,同时TextureLayer
要求 Flutter Mini SDK API 23 - 3.3 开始
VirtualDisplays
实现又回来了,因为需要支持 Mini SDK API 23 以下的场景,同时比如SurfaceView
机制原因,这类场景TextureLayer
实现没办法直接兼容,所以 3.3 开始在遇到SurfaceView
实现时TextureLayer
会切换为VirtualDisplays
模式
这里面固然有实现成本的问题,同时也有社区把控的问题,才造成了这样“混乱”的局面。
上面这么说可能有些抽象,我们再看具体例子,首先看官方的 webview_flutter 实现, 如下表格所示,最初的时候 WebView 插件默认是使用了 AndroidWebView
,也就是 VirtualDisplays
相关的实现;后来有了 HybridComposition
之后就新增了 SurfaceAndroidWebView
,用户在需要时可以根据场景自行切换。
默认判断 | VirtualDisplays | HybridComposition |
---|---|---|
这里就不得不提以前的 issue ,如果你的 WebView 存在触摸或者键盘问题就可以通过下述代码来解决,因为正如前面说过, HybridComposition
是通过 addView
的方式添加原生控件,所以能尽可能保存控件本身的特性。
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
而后来 webview_flutter 在更新迭代里默认实现里也修改为了 SurfaceAndroidWebView
,因为 HybridComposition
确实更适合 WebView 的场景,比如 H5 的详情页。
默认判断 | TextureLayer | HybridComposition |
---|---|---|
上面的例子可能还不够典型,接着我们再看一个典型的例子: MapView ,如下所示,目前国内 Flutter 里常见的地图实现有:
虽然他们都是采用了 PlatformView
的实现,但是选择的实现方式却存在一定差异,而通过这些差异也可以很好比对 PlatformView
实现的区别:
- 高德 Flutter 插件采用的是
AndroidView
的实现方式,也就是在 3.0 以下是VirtualDisplays
,在 3.0 以上是TextureLayer
, 从表中的图 2 也可以看到在 Flutter 3.0 MapView 父容器对应的TextureLayer
的PlatformViewWrapper
。
dart 实现 | TextureLayer |
---|---|
这里额外需要注意的是:由于隐私政策等原因,现在基本地图框架都需要做一定的隐私相关适配,高德也不例外,所以如果想正常使用 SDK ,就需要注意初始化时的适配问题。
对隐私和权限问题感兴趣可以看 《2022 年 App 上架审核问题集锦,全面踩坑上线不迷路》
- 华为 Flutter 地图插件选择的则是
HybridComposition
的实现方式,注意下图代码里的initExpensiveAndroidView
,因为现在使用PlatforViewLink
已经不能作为是否使用HybridComposition
的判定标准,3.0 开始必须显式配置了initExpensiveAndroidView
才是HybridComposition
。
3.0之前 PlatforViewLink |
3.0之后 PlatforViewLink + initSurfaceAndroidView |
3.0之后 PlatforViewLink + initExpensiveAndroidVie |
---|---|---|
HybridComposition |
TextureLayer |
HybridComposition |
对
HybridComposition
详细实现感兴趣的可以了解:混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进 和 《1.20 下的 Hybrid Composition 深度解析》
- 百度 Flutter 地图插件采用的是
AndroidView
的实现方式,但是原生端提供了TextureView
和SurfaceView
两种实现选择,这又产生了不同的组合结果。
3.0之前 | 3.0 | 3.0之后 |
---|---|---|
VirtualDisplays |
TextureLayer ( SurfaceView 方案无法工作) |
VirtualDisplays (SurfaceView)/ TextureLayer (TextureView) |
从下方的 Layout Inspector 可以看到,在 Flutter 3.3 下,百度地图插件如果使用了 MapSurfaceView
就会采用 VirtualDisplays
,而如果使用了 MapTextureView
就会采用 TextureLayer
,这是为什么呢?
SurfaceView | TextureView |
---|---|
前面我们不是说过,Flutter 在 3.0 的时候通过 TextureLayer
取代了 VirtualDisplays
,而 TextureLayer
的实现方式就是通过 PlatformViewWrapper
来替换 child 的 Canvas 从而提取纹理,但是在某些场景下就会有问题,例如 SurfaceView
。
简单来说,
SurfaceView
是比较特殊的场景,本身它具备双缓冲机制,类似于两个Canvas
,同时在 WMS 有独立的WindowState
, 所以渲染逻辑其实是脱离了原本的 View hierachy ,因此 Flutter 3.0 下的TextureLayer
的 hook Canvas 实现没办法兼容。
所以在 Flutter 3.3 中 VirtualDisplays
又回来了,作为多年服役的"老将",当运行平台低于 23 或者原生控件是 SurfaceView
时,就会继续降级采用 VirtualDisplays
的相关实现,这也算是折中的实现。
如果对这部分感兴趣的可以查阅 Flutter 3.0下的混合开发演进 / Flutter 深入探索混合开发的技术演进
所以现在知道为什么要做这一期了吧?Flutter 的 PlatformView
实现因为历史包袱留下了很多“坑”,不理清楚这些坑,很可能就会让你在遇到问题时找不到方向,总结一下:
VirtualDisplays
老骥伏枥,采用的是虚拟显示提取纹理的方式,可能存在手势和键盘问题,适合列表和小控件场景,在 3.0 版本中缺席HybridComposition
在 1.2 版本之后出现,因为是通过addView
和层级堆叠实现混合,能尽可能保证控件的“原汁原味”,但是性能开销会比较大,不适合列表里使用,在 3.0 之后需要显式调用initExpensiveAndroidView
TextureLayer
在 3.0 版本开始出现,通过 Hook 住控件 Canvas 的方式实现纹理提取,但是需要 miniSDK 23 ,适用于替代VirtualDisplays
的场景,但是在 3.3 开始遇到SurfaceView
时会降级为VirtualDisplays
。
其实我有预感,在之后的版本这部分逻辑还会出现变化,但是可能 3.0 和 3.3 的发布一样,并不会将这种变更放在 release note 里。
最后,这里额外介绍一个关于地图的小知识点,现在国内三大地图 SDK 厂商都有商用 5 万起步的授权费用要求,如果你打算用在公司的项目里,那么这个是逃不掉的,因为迟早平台会打电话联系你缴费,所以在评估时最好将这个费用问题和产品沟通下。
高德 | 百度 | 腾讯 |
---|---|---|
当然,华为的地图服务目前看起来没有商业授权的要求,但是它打包配置麻烦,特别是需要 HMS 的场景,所以如果为了省点钱又不怕麻烦,也可以考虑华为地图。
那有人就要说了,我自己做一个行不行?我比较牛,有数据有能力自己做个简易版地图不就免费了?答案时不行,如下所示,应用平台会有关于地图相关的资质要求,如果没有你做出来了也上不去。
审核意见:我们发现您的应用内含有互联网地图服务相关内容,依据《关于加强互联网地图管理工作的通知》互联网地图服务单位应当依法取得相应的互联网地图服务测绘资质,并在资质许可的范围内提供互联网地图服务。 修改建议:请补充提供《测绘资质证书》并在专业范围中注明互联网地图服务;若接入第三方开放平台地图服务的,可提供双方合作协议(授权证明),同时需在地图显著位置补充标明合作单位。
好了,本篇就到这里结束了,可能相对而言会比之前的内容更“技术性”一些,但是本质还是希望可以帮助大家搞清楚 PlatformView
在选型上的区别,最后,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,