GSYFlutterBook/Z6.md

165 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 工程或者框架的疑问,欢迎留言评论,