GSYFlutterBook/Z6.md

165 lines
16 KiB
Markdown
Raw Permalink Normal View History

2023-01-04 09:38:40 +08:00
# Flutter 工程化框架选择 — 混合开发的摸爬滚打
```
本文为稀土掘金技术社区首发签约文章14天内禁止转载14天后未获授权禁止转载侵权必究
```
这是 《Flutter 工程化框架选择》 系列的第四篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,可以说这是一个指引系列,所以比较适合新手同学。
> **本篇将着重介绍混合开发里的一些技术选型,顺便额外科普一些坑,看到后面你就会明白为什么要做这一期分类,算是相对偏技术的一期**。
在 Flutter 里进行混合开发一直都是“痛点”,其中最主要的原因就是:**Flutter 有自己独立的渲染引擎,因为 Flutter 控件渲染脱离了平台控件的 render tree这也造成了 Flutter 在混合开发上的实现相对复杂了不少**。
> 比较形象的理解:类似于把原生控件塞到 WebView 里渲染。
事实上在 Flutter 里混合开发也分为两种:**在 Flutter 里混合原生平台控件**`PlatformView`)和**将 Flutter 混合到原生平台项目(`add-to-app `**,今年我们的主题是 `PlatformView` ,这其中又以 Android 的 `PlatformView` 混合原生控件“故事最多”,当然今天我们不是从头去讲解它的技术实现,对历史技术实现感兴趣的可以看之前的文章,今天主要是聊如何去“选“。
> - [Flutter 3.0下的混合开发演进](https://juejin.cn/post/7113655154347343909)
> - [Flutter 深入探索混合开发的技术演进](https://juejin.cn/post/7093858055439253534)
> - [混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进](https://juejin.cn/post/7071549421116194847)
> - [《1.20 下的 Hybrid Composition 深度解析》](https://juejin.cn/post/6858473695939084295)
> - [《 Android PlatformView 和键盘问题》](https://juejin.cn/post/6844904070906380296)
# PlatformView
在 Flutter 里混合原生平台的控件需要通过 `PlatformView` 进行接入实现,**在这方面 iOS 的实现方式一直变化不大,但是在 Android 上不同版本到目前就出现了三种实现逻辑**,所以本篇主要介绍 Android 平台如何选择合适的 `PlatformView` 方案。
抛开最早期如 [flutter_webview_plugin](https://pub.dev/packages/flutter_webview_plugin) 这种直接进行覆盖原生控件的实现不谈, `PlatformView` 在 Android 上目前官方提供的就有三种实现方式:
- **`VirtualDisplays`** :类似于一个虚拟显示区域,将虚拟显示区域的内容渲染在一个内存 `Surface`上,然后同步纹理到 Engine ,事实上控件并不在渲染位置。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image1.png)
- **`HybridComposition`** 通过 `addView` 直接添加原生平台控件,需要层级覆盖时利用 `FlutterImageView` 承载 Flutter 控件的渲染进行堆叠,原生控件会在渲染位置。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image2.png)
- **`TextureLayer`** :在渲染位置通过原生的透明 ` PlatformViewWrapper` 做容器,然后通过替换 Canvas 将原生控件的纹理渲染到特定 Surface 上,控件不在渲染位置,但是可以通过父容器做事件拦截。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image3.png)
> 是不是看完有点懵?不怕后面会有对应的例子解释,不理解也不要紧,这一篇是教你如何选,所以也并不需要真的理解内部是如何实现。
但是上面介绍的这三种实现也是有前提条件的,比如:
- 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](https://pub.dev/packages/webview_flutter) 实现, 如下表格所示,最初的时候 WebView 插件默认是使用了 `AndroidWebView` ,也就是 `VirtualDisplays` 相关的实现;后来有了 `HybridComposition` 之后就新增了 `SurfaceAndroidWebView` ,用户在需要时可以根据场景自行切换。
| 默认判断 | VirtualDisplays | HybridComposition |
| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image4.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image5.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image6.png) |
这里就不得不提以前的 issue ,如果你的 WebView 存在触摸或者键盘问题就可以通过下述代码来解决,因为正如前面说过, `HybridComposition` 是通过 `addView` 的方式添加原生控件,所以能尽可能保存控件本身的特性。
```dart
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
```
而后来 webview_flutter 在更新迭代里默认实现里也修改为了 `SurfaceAndroidWebView` ,因为 `HybridComposition` 确实更适合 WebView 的场景,比如 H5 的详情页。
| 默认判断 | TextureLayer | HybridComposition |
| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image7.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image8.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image9.png) |
上面的例子可能还不够典型,接着我们再看一个典型的例子: MapView ,如下所示,目前国内 Flutter 里常见的地图实现有:
- 华为:[huawei_map](https://pub.dev/packages/huawei_map)
- 高德:[amap_flutter_map](https://pub.flutter-io.cn/packages/amap_flutter_map)
- 百度: [flutter_baidu_mapapi_map ](https://pub.flutter-io.cn/packages/flutter_baidu_mapapi_map)
虽然他们都是采用了 `PlatformView` 的实现,但是选择的实现方式却存在一定差异,而通过这些差异也可以很好比对 `PlatformView` 实现的区别:
- [高德 Flutter 插件](https://developer.amap.com/api/flutter/guide/map-flutter-plug-in/map-flutter-info)采用的是 `AndroidView` 的实现方式,也就是在 3.0 以下是 `VirtualDisplays` ,在 3.0 以上是 `TextureLayer` 从表中的图 2 也可以看到在 Flutter 3.0 MapView 父容器对应的 `TextureLayer`` PlatformViewWrapper`
| dart 实现 | TextureLayer |
| ----------------------------------------------------------- | ----------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image10.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image11.png) |
这里额外需要注意的是:**由于隐私政策等原因,现在基本地图框架都需要做一定的隐私相关适配**,高德也不例外,所以如果想正常使用 SDK ,就需要注意初始化时的适配问题。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image12.png)
> 对隐私和权限问题感兴趣可以看 [《2022 年 App 上架审核问题集锦,全面踩坑上线不迷路》](https://juejin.cn/post/7142363251911688222)
- [华为 Flutter 地图插件](https://pub.dev/packages/huawei_map)选择的则是 `HybridComposition` 的实现方式,注意下图代码里的 `initExpensiveAndroidView ` ,因为**现在使用 `PlatforViewLink` 已经不能作为是否使用 `HybridComposition` 的判定标准**3.0 开始必须显式配置了 `initExpensiveAndroidView ` 才是 `HybridComposition`
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image13.png)
| 3.0之前 `PlatforViewLink` | 3.0之后 `PlatforViewLink` + `initSurfaceAndroidView` | 3.0之后 `PlatforViewLink` + `initExpensiveAndroidVie` |
| -------------------------- | ------------------------------------------------------ | ------------------------------------------------------- |
| `HybridComposition` | `TextureLayer` | `HybridComposition` |
> 对 `HybridComposition` 详细实现感兴趣的可以了解:[混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进](https://juejin.cn/post/7071549421116194847) 和 [《1.20 下的 Hybrid Composition 深度解析》](https://juejin.cn/post/6858473695939084295)
- [百度 Flutter 地图插件](https://lbsyun.baidu.com/index.php?title=flutter/loc/create-project/configure)采用的是 `AndroidView` 的实现方式,但是原生端提供了 `TextureView``SurfaceView` 两种实现选择,这又产生了不同的组合结果。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image14.png)
| 3.0之前 | 3.0 | 3.0之后 |
| ----------------- | ---------------------------------------------- | ------------------------------------------------------------ |
| `VirtualDisplays` | `TextureLayer` `SurfaceView` 方案无法工作) | `VirtualDisplays`SurfaceView/ `TextureLayer`TextureView |
从下方的 Layout Inspector 可以看到,在 Flutter 3.3 下,**百度地图插件如果使用了 `MapSurfaceView` 就会采用 `VirtualDisplays` ,而如果使用了 `MapTextureView` 就会采用 `TextureLayer` ,这是为什么呢**
| SurfaceView | TextureView |
| ----------------------------------------------------------- | ----------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image15.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image16.png) |
前面我们不是说过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` 的相关实现,这也算是折中的实现**。
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image17.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image18.png) |
| ----------------------------------------------------------- | ----------------------------------------------------------- |
> 如果对这部分感兴趣的可以查阅 [Flutter 3.0下的混合开发演进](https://juejin.cn/post/7113655154347343909) / [Flutter 深入探索混合开发的技术演进](https://juejin.cn/post/7093858055439253534)
所以现在知道为什么要做这一期了吧?**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 万起步的授权费用要求**,如果你打算用在公司的项目里,那么这个是逃不掉的,因为迟早平台会打电话联系你缴费,所以在评估时最好将这个费用问题和产品沟通下。
| 高德 | 百度 | 腾讯 |
| ----------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image19.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image20.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image21.png) |
| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image22.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image23.png) | ![image-20221009093013634](http://img.cdn.guoshuyu.cn/20221011_Z6/image24.png) |
当然,华为的地图服务目前看起来没有商业授权的要求,但是它打包配置麻烦,特别是需要 HMS 的场景,所以如果为了省点钱又不怕麻烦,也可以考虑华为地图。
![](http://img.cdn.guoshuyu.cn/20221011_Z6/image25.png)
那有人就要说了,我自己做一个行不行?我比较牛,有数据有能力自己做个简易版地图不就免费了?答案时不行,如下所示,应用平台会有关于地图相关的资质要求,如果没有你做出来了也上不去。
> **审核意见:我们发现您的应用内含有互联网地图服务相关内容,依据《关于加强互联网地图管理工作的通知》互联网地图服务单位应当依法取得相应的互联网地图服务测绘资质,并在资质许可的范围内提供互联网地图服务。 ­修改建议:请补充提供《测绘资质证书》并在专业范围中注明互联网地图服务;若接入第三方开放平台地图服务的,可提供双方合作协议(授权证明),同时需在地图显著位置补充标明合作单位。**
好了,本篇就到这里结束了,可能相对而言会比之前的内容更“技术性”一些,但是本质还是希望可以帮助大家搞清楚 `PlatformView` 在选型上的区别,最后,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,