Flutter 工程化框架选择 — 混合开发的摸爬滚打

7,906 阅读9分钟
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

这是 《Flutter 工程化框架选择》 系列的第四篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,可以说这是一个指引系列,所以比较适合新手同学。

本篇将着重介绍混合开发里的一些技术选型,顺便额外科普一些坑,看到后面你就会明白为什么要做这一期分类,算是相对偏技术的一期

在 Flutter 里进行混合开发一直都是“痛点”,其中最主要的原因就是:Flutter 有自己独立的渲染引擎,因为 Flutter 控件渲染脱离了平台控件的 render tree,这也造成了 Flutter 在混合开发上的实现相对复杂了不少

比较形象的理解:类似于把原生控件塞到 WebView 里渲染。

事实上在 Flutter 里混合开发也分为两种:在 Flutter 里混合原生平台控件PlatformView)和将 Flutter 混合到原生平台项目(add-to-app ,今年我们的主题是 PlatformView ,这其中又以 Android 的 PlatformView 混合原生控件“故事最多”,当然今天我们不是从头去讲解它的技术实现,对历史技术实现感兴趣的可以看之前的文章,今天主要是聊如何去“选“。

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 之前,你只能用 VirtualDisplaysHybridComposition
  • Flutter 3.3 之前,你只能用 TextureLayerHybridComposition
  • Flutter 3.3 开始,你能用 VirtualDisplaysTextureLayerHybridComposition

是不是觉得有些奇怪,为什么会有奇怪的排列方式?这里面的故事线大概是这样的:

  • 因为 VirtualDisplays 存在触摸事件和键盘等问题,所以通过 addView 进行原生堆叠的 HybridComposition 出现,解决了手势和键盘等问题
  • 因为 HybridComposition 存在一些场景缺陷,比如列表卡顿,渲染线程不同步导致闪动等问题,所以 VirtualDisplays 得以继续服役
  • 3.0 开始默认 VirtualDisplaysHybridComposition 被替换为 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 ,用户在需要时可以根据场景自行切换。

默认判断VirtualDisplaysHybridComposition

这里就不得不提以前的 issue ,如果你的 WebView 存在触摸或者键盘问题就可以通过下述代码来解决,因为正如前面说过, HybridComposition 是通过 addView 的方式添加原生控件,所以能尽可能保存控件本身的特性。

if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();

而后来 webview_flutter 在更新迭代里默认实现里也修改为了 SurfaceAndroidWebView ,因为 HybridComposition 确实更适合 WebView 的场景,比如 H5 的详情页。

默认判断TextureLayerHybridComposition

上面的例子可能还不够典型,接着我们再看一个典型的例子: 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之前 PlatforViewLink3.0之后 PlatforViewLink + initSurfaceAndroidView3.0之后 PlatforViewLink + initExpensiveAndroidVie
HybridCompositionTextureLayerHybridComposition

HybridComposition 详细实现感兴趣的可以了解:混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进《1.20 下的 Hybrid Composition 深度解析》

  • 百度 Flutter 地图插件采用的是 AndroidView 的实现方式,但是原生端提供了 TextureViewSurfaceView 两种实现选择,这又产生了不同的组合结果。

3.0之前3.03.0之后
VirtualDisplaysTextureLayerSurfaceView 方案无法工作)VirtualDisplays(SurfaceView)/ TextureLayer(TextureView)

从下方的 Layout Inspector 可以看到,在 Flutter 3.3 下,百度地图插件如果使用了 MapSurfaceView 就会采用 VirtualDisplays ,而如果使用了 MapTextureView 就会采用 TextureLayer ,这是为什么呢

SurfaceViewTextureView

前面我们不是说过,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 万起步的授权费用要求,如果你打算用在公司的项目里,那么这个是逃不掉的,因为迟早平台会打电话联系你缴费,所以在评估时最好将这个费用问题和产品沟通下。

高德百度腾讯
image-20221009093013634

当然,华为的地图服务目前看起来没有商业授权的要求,但是它打包配置麻烦,特别是需要 HMS 的场景,所以如果为了省点钱又不怕麻烦,也可以考虑华为地图。

那有人就要说了,我自己做一个行不行?我比较牛,有数据有能力自己做个简易版地图不就免费了?答案时不行,如下所示,应用平台会有关于地图相关的资质要求,如果没有你做出来了也上不去。

审核意见:我们发现您的应用内含有互联网地图服务相关内容,依据《关于加强互联网地图管理工作的通知》互联网地图服务单位应当依法取得相应的互联网地图服务测绘资质,并在资质许可的范围内提供互联网地图服务。 ­修改建议:请补充提供《测绘资质证书》并在专业范围中注明互联网地图服务;若接入第三方开放平台地图服务的,可提供双方合作协议(授权证明),同时需在地图显著位置补充标明合作单位。

好了,本篇就到这里结束了,可能相对而言会比之前的内容更“技术性”一些,但是本质还是希望可以帮助大家搞清楚 PlatformView 在选型上的区别,最后,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,