有关 Flutter 混和 PlatformView 的同时实现已经如是说过三次,随着 5 月末Google IO 的吻合,捷伊 PlatformView 同时实现如果也会接踵而至,此次就自始至终来两个详尽的有关 PlatformView 的重构归纳。
❝Flutter 作为第三代的虚拟化架构,透过自订图形发动机的控制技术创新大大降低了虚拟化的操控性和连续性,但也便是即使这两点, 相对而言 Flutter 在混和合作开发时对于原生植物控件的全力支持生产成本更高。
❞Flutter 混和合作开发的症结
具体来说 Flutter 在混和合作开发中最大的症结就是它分立的图形发动机,举两个不是很正确的范例:
❝Flutter 里混和合作开发类似于与把原生植物命令行图形到 WebView 里。
❞大体上在 Flutter 里混和合作开发的觉得是这种,即使 「Flutter UI 不会切换为原生植物命令行,而要由 Flutter Engine 使用 Skia 间接图形在 Surface 上」。
因此 Flutter 在最先出时并不全力支持 WebView 或 MapView 那些常见的命令行,这也引致了彼时 Flutter 曾一度的B110不太好,因此派生出了第二代非正式的混和合作开发全力支持,比如: flutter_webview_plugin 。
「在非官方 WebView 命令行全力支持出以后」 ,服务器端是间接在 FlutterView 上全面覆盖了两个捷伊原生植物命令行,借助 Dart 中的转义命令行来「传达位置和大小不一」。
❝Flutter 里基本上大部份图形都是图形到 FlutterView 这种两个白眉林底下,因此间接全面覆盖两个捷伊原生植物 WebView 根本无法说减轻解困。
❞如下表所示图,在 Flutter 端 push 出两个 「预设好边线和大小不一」 的 SingleChildRenderObjectWidget ,进而得到需要显示的大小不一和边线,将那些重要信息透过 MethodChannel 传达到原生植物层,在原生植物层 addContentView 两个选定大小不一和边线的 WebView 。
这种看上去就像在 Flutter 中加进了 WebView ,但前述这种的操作方式根本无法说是“救命”,「即使这种的犯罪行为瓦解了 Flutter 的图形树」,其中两个问题是:
❝当你跳转 Flutter 其他页面的时候会被当前原生植物的 WebView 挡住;并且打开页面的动画时Appbar 和 WebView 难以保持一致,即使 Appbar 和 WebView 是出于两个动画体系和图形体系。
❞就比如打开了捷伊 Flutter UI 2 页面,但是由于它还是在 FlutterView 内,因此它会被 WebView所遮挡。
image-20220307175357032但是这个“转义”显示的思路,也起到了一定的作用,在后续 Flutter 全力支持原生植物 PlatformView 上起到了带头的作用。
Flutter 初步全力支持原生植物命令行
为了让 Flutter 真正走向大众化,非官方开始推出了非官方基于 PlatformView 的系列同时实现,比如: webview_flutter ,而这个同时实现 “缝缝补补” 也被沿用至今,成了 Flutter 接入原生植物的方式 之一。
Android
在 PlatformView 的整个同时实现中 Android 坑一直是最多的,即使一开始 Android 上主要是透过 AndroidView 做完成这项工作,而它的 Virtual Displays 同时实现其实并不友好。
「在 Flutter 中会将 AndroidView 需要图形的内容绘制到 VirtualDisplays 中 ,然后在 VirtualDispl
❝VirtualDisplay 类似于于两个虚拟显示区域,需要结合 DisplayManager 一起调用,一般在副屏显示或者录屏场景下会用到,在 VirtualDisplay 里会将虚拟显示区域的内容图形在两个 Surface 上。
❞image-20220307180859494如上图所示,「简单来说是原生植物命令行的内容被绘制到置和大小不一参数。
式插入 Android 原生植物命令行。
iOS
在 iOS 平台上就不使用类似于 VirtualDisplay 的方法,而要「透过将 Flutter UI 分为两个透明纹理来完成组合:两个在 iOS 平台视图之下,两个在其上面」。
因此这种的好处是:
需要在 “iOS平台” 视图下方呈现的Flutter UI,最终会被绘制到其下方的纹理上;而需要在 “平台” 上方呈现的 Flutter UI,最终会被绘制在其上方的纹理;iOS 上「它们只需要在最后组合起来就可以了」,通常这种方法更好,即使这意味着 Native View 可以间接加进到 Flutter 的 UI 层次结构中,但是可惜一开始 Android 平台并不全力支持这种模式。
问题
尽管前面可以使用 VirtualDisplay 将 Android 命令行嵌入到 Flutter UI 中 ,但这种 VirtualDisplay 这种介入还有其他麻烦的问题需要处理。
触摸事件
「默认情况下, PlatformViews 是没办法接收触摸事件」,即使 AndroidView 其实是被图形在 VirtualDisplay 中 ,而每当用户点击看到的 “AndroidView” 时,其实他们就真正”点击的是正在图形的 Flutter 纹理 ,**用户产生的触摸事件是间接发送到 Flutter View 中,而不是他们前述点击的 AndroidView**。
所以 AndroidView 使用 Flutter Framework 中检测用户的触摸是否在需要的特殊处理的区域内:
❝当触摸成功时会向 Android embedding 发送一条消息,其中包含 touch 事件的详尽重要信息。
❞这就变成有些本末倒置,触摸事件从原生植物-Flutter-原生植物,中间的转化引致某些重要信息被丢失,也引致了响应的延迟。
文字输入
irtualDisplay 所在的边线会始终被认为是 unfocused 的状态」。
因此需要做一套代理来处理 InputConnections 做输入,甚至这个犯罪行为在 WebView 上更复杂,即使 WebView 具有自己内部的逻辑来创建和设置输入连接,而那些输入连接并没有完全遵循 Android 的协议。
同步问题
另外还需要处理各种同步问题,比如非官方就创建了两个 SurfaceTextureWrapper 用于处理同步的问题。
即使当承载 AndroidView 的 SurfaceTexture 被释放时,由于 SurfaceTexture.release是在 platform 线程被调用,而 attachToGLContext 是在 raster 线程被调用,不同线程调用时可能引致:「当 attachToGLContext 被调用时 texture 已经被释放了,因此需要 SurfaceTextureWrapper 用于同时实现 Java 里同步锁的效果」。
Flutter Hybrid Composition
因此经历了 Virtual Display 的折磨之后,非官方终于在后续推出了更为合理的同时实现。
同时实现逻辑
hybrid composition 的出现给 Flutter 提供了一种捷伊混和思路,「那是间接把原生植物命令行加进到 Flutter 里一起组合图形」。
具体来说简单如是说下使用,比起 Virtual Display 间接使用 AndroidView ,hybrid composition 相对会复杂一点点,dart 里使用到 PlatformViewLink 、AndroidViewSurface 、 PlatformViewsService 这三个对象。
正常在 dart 层面,使用 hybrid composition 接入原生植物命令行:
透过 PlatformViewLink 的 viewType 注册了两个和原生植物层对应的注册名称,这和以后的 PlatformView 注册一样;然后在 surfaceFactory 返回两个 AndroidViewSurface 用于处理绘制和接收触摸事件,同时也是两个类似于转义的作用;最后在 onCreatePlatformView 方法使用 PlatformViewsService 初始化 AndroidViewSurface 和初始化所需要的参数,同时透过 Engine 去触发原生植物层的显示。看上去好像把两个 AndroidView 完成的事情变得相对复杂了,但是其实 hybrid composition 的同时实现相比其实更好理解。
使用 hybrid composition 之后, 「PlatformView 就间接透过 FlutterMutatorView(两个特殊的 FrameLayout) 把原生植物命令行 addView 到 FlutterView上,然后再透过 FlutterImageView 的能力去同时实现多图层的混和」。
不理解吗?没事,我们后面会详尽如是说,先简单解释是:
❝Flutter 只间接透过原生植物的 自己的 Widget 时,Flutter 就会透过再叠加两个 FlutterImageView 来承载这个 Flutter Widget 。
❞image-20220308173844917深入细致范例详解
接下来让我们从前述范例去理解 Hybrid Composition ,结合 Andriod Studio 的 Layout Inspector,并开启手机的绘制边界来看会更直观。
如下表所示代码所示,一般情况下我们运行之后会看到一片黑色,即使这时候 FlutterView 只有两个 FlutterSurfaceView 的子命令行存在,此时虽然我们画底下是有两个 Flutter 的红色 RE 文本命令行 ,不过即使是由 Flutter 间接在 Surface 间接绘制,因此 Layout Inspector 看不到只显示黑色。
此时我们加进两个透过 Hybrid Composition 同时实现两个原生植物的 TextView 命令行,透过 PlatformView 在 Flutter 上图形出两个灰色 RE 文本。
可以看到,如上图所示,在我们的显示布局边界上可以清晰看到它的重要信息:
❝TextView 透过 FlutterMutatorView 被加进到 FlutterView 上被间接显示出。
❞「因此 TextView 是间接在原生植物代码上被 add 到 FlutterView 上,而不是提取纹理」,另外可以看到,左侧栏里多了两个 FlutterImageView ,并且以后看不到的 Flutter 命令行红色 RE 文本也出现了,背景也变成了 Flutter 上的白色。
我们先暂时忽略新出现的 FlutterImageView 和 Flutter UI 能够出现在 Layout Inspector 的原因,留到后面再来分析,此时我们再新增加以两个红色的 Flutter RE 命令行到 Stack 里,位于 PlatformView 的灰色 RE 下。
如上图所示,可以看到布局和图形效果正常,Flutter 的红色 RE 被上面的 PlatformView 灰色 RE遮挡了部分,这是符合代码的图形效果。
如果这时候我们把新增加的第二个红色 RE 放到灰色 PlatformView 灰色 RE 上,会发生什么情况?
可以看到红色的 RE 成功被图形到灰色 RE 上 ,而之因此能够图形上去的原因,是即使这个和 PlatformView 有交集的 Text ,被图形到两个新增的 FlutterImageView 命令行上, 也是 Flutter 判断了此时新红色 RE 文本需要图形到 PlatformView 上,因此加进了两个 FlutterImageView用于承载这部分图形内容。
如果这时候挪动第二个红色的 RE 让它和 PlatformView 没有交集,但是还是在 Stack 里位于 PlatformView 之上会如何?
可以看到虽然 FlutterImageView 没了,第二个红色的 RE 也回到了默认的 Surface上,因此这是 Hybrid Composition 混和原生植物命令行的最基础设计理念:
「间接把原生植物命令行加进到 FlutterView 之上」;「原生植物和 Flutter 命令行混和堆叠时,用捷伊 FlutterImageView 来同时实现层级全面覆盖;」「如果没有交集就不需要捷伊 FlutterImageView;」有关 FlutterImageView 后面再展开,我们继续这个范例,让两个 Flutter 的红色 RE 都图形到 PlatformView 的灰色的RE 上会是什么情况?
如上图所示,可以看到两个红色的 Flutter RE 命令行共享了两个 FlutterImageView ,这里可以得到两个捷伊结论:**和 PlatformView 有交集的同层级 Flutter 命令行会同享同两个 FlutterImageView 。 **
我们继续调整示例,如下表所示代码我们新增多两个 PlatformView 的灰色 RE 命令行,然后调整边线,但是 Flutter 命令行都在两个层级上,运行之后可以看到,只要 Flutter 命令行都在同两个层级,就同享同两个 FlutterImageView 。
但是如果不在两个层级呢?我们调整两个灰色 RE 的边线,让 PlatformView 的灰色 RE 和 Flutter 的红色 RE 交替出现。
可以看到,两个红色的 Flutter RE 命令行都单独被图形都两个 FlutterImageView 上,因此我们有捷伊结论:「和 PlatformView 有交集的 Flutter 命令行如果在不同层级,就需要不同的 FlutterImageView 来承载。」
因此一般在使用 PlatformView 的场景上,不建议有过多的层级堆叠或者过于复杂的 UI 场景。
接着我们继续测试,还记得前面说过 Virtual Display 上有关触摸事件的问题,因此此时我们间接给 PlatformView 的 灰色 RE 在原生植物层加进点击事件弹出 Toast 测试。
可以看到运行后点击能够正常弹出 Toast ,因此对于 PlatformView 来说本身的点击和触摸是可以正常保留,然后我们调整下红色大 RE 和灰色 RE 让他们产生交集,同时给红色的大 RE 也加进点击事件,弹出 SnackBar 。
运行之后可以看到,点击没有被全面覆盖的灰色部分,还是可以弹出 Toast ,点击红色 RE 和灰色 RE 的交集处,可以正常弹出 SnackBar。
因此可以看到 「Hybrid Composition 上这种同时实现,能更原汁原味地保流下原生植物命令行的事件和特性,即使从原生植物 角度,它是原生植物层面的物理堆叠」。
现在大家如果大体对于 Hybrid Composition 有了一定理解,那回到前面那个一开始 Layout InSpector 黑屏 ,后来又能图形出界面的原因,这就和首次加进 Hybrid Composition 时多出的 FlutterImageView 有关系。
如下表所示图所示,可以看到此时原生植物的灰色 RE 和 Flutter 的红色 RE 是没有交集的,为什么会多出两个 FlutterImageView 呢?
image-20220309093557122这就需要说到 flutterView.convertToImageView() 这个方法。
在 Flutter 图形 Hybrid Composition 的 PlatformView 时,会有两个 flutterView.convertToImageView() 的操作方式,这个操作是:「把默认的 FlutterSurfaceView 图形切换到 FlutterImageView 上」 ,因此此时会有两个 新增的 FlutterImageView 出现在 FlutterSurfaceView 之上。
❝为什么需要 FlutterImageView ?那就要先理解下 FlutterImageView 是如何工作的,即使在前面我们说过,和 PlatformView 有交集的时候 Flutter 的命令行也会被图形到 FlutterImageView 上。
❞FlutterImageView 本身是两个原生植物的 Android View 命令行,它的内部有几个关键对象:
imageReader :提供两个 surface ,并且能够间接访问到 surface 里的图像数据;flutterRenderer : 外部传入的 Flutter 图形类,这里用于切换/提供 Flutter Engine 里的图形所需 surface ;currentImage : 从 imageReader 里提取出的 Image 画面;currentBitmap :将 Image 转为 Bitmap ,用于 onDraw 时绘制;因此简单地说 FlutterImageView 工作机制是:「透过 imageReader 提供 surface 给 Engine 图形,然后把 imageReader 里的画面提取出,图形到 FlutterImageView 的 onDraw 上」。
因此回归到前面的 flutterView.convertToImageView() ,在 Flutter 图形 Hybrid Composition 的 PlatformView 时,会先把自己也变成了两个 FlutterImageView ,然后进入捷伊图形流程:
Flutter 在 onEndFrame 时,也就是每帧结束时,会判断当前界面是否还有 PlatformView ,如果没有就会切换会默认的 FlutterSurfaceView ;如果还存在 PlatformView ,就会调用 acquireLaew 执行 onDraw ,进而把 currentBitmap 里的内容绘制出。image-20220308171209135因此我们搞清楚了 FlutterImageView 的作用,也搞清楚了为什么有了 Hybrid Composition 的 PlatformView 之后,在 Android Studio 的 Layout Inspector 里可以看到 Flutter 命令行的原因:
❝「即使有 Hybrid Composition 之后, FlutterSurfaceView 变成了 FlutterImageView ,而 FlutterImageView 绘制是透过 onDraw ,因此可以在 Layout Inspector 里出现。」
❞那为什么会有把 FlutterSurfaceView 变成了 FlutterImageView 这种的操作方式?「原因其实是为了更好的动画同步和图形效果」。
即使前面说过,Hybrid Composition 是间接把加进到 FlutterView 上面,因此走的还是原生植物的图形流程和时机,而这时候透过把 FlutterSurfaceView 变成了 FlutterImageView ,也是把 Flutter 命令行图形也同步到原生植物的 OnDraw 上,这种对于画面同步会更好。
那有人就要说了,我就不喜欢 FlutterImageView 的实现,有没有办法不在使用 Hybrid Composition 时把 FlutterSurfaceView 变成了 FlutterImageView 呢?
有的,非官方在 PlatformViewsService 内提供了对应的设置全力支持:
在设置为 false 之后,可以看到只有 Hybrid Composition 的 PlatformView 的内容才能在 Layout Inspector 上看到,而 FlutterSurfaceView 看上去是黑色空白。
image-20220308151751781image-20220308151856383问题
那 Hybrid Composition 是完美吗? 肯定不是,事实上 Hybrid Composition 也有很多小问题,其中就比如操控性问题。
比如在不使用 Hybrid Composition 的情况下,Flutter App 中 UI 是在特定的光栅线程运行,因此 Flutter 上 App 本身的主线程很少受到阻塞。
「但是在 Hybrid Composition 下,Flutter UI 会由平台的 onDraw 绘制,这可能会引致一定程度上需要消耗平台操控性和占用通信的开销」。
比如在 Android 10 以后, Hybrid Composition 需要将内存中的每个 Flutter 绘制的帧数据复制到主内存,之后再从 GPU 图形复制回来 ,因此也会引致 Hybrid Composition 在 Android 10 以后的操控性表现更差,比如在滚动列表里每个 Item 嵌套两个 Hybrid Composition 的 PlatformView 。
❝具体体现在 ImageReader 创建时,大于 29 的可以使用 HardwareBuffer ,而HardwareBuffer 允许在不同的应用程序进程之间共享缓冲区,透过 HardwareBuffers可以映射各种硬件系统的可访问 memory,比如 GPU。
❞image-20220309153648439**因此如果当 Flutter 出现动画卡顿时,或者你就如果考虑使用 Virtual Display 或者禁止 FlutterSurfaceView 变成了 FlutterImageView**。
❝事实上 Virtual Display 的操控性也不好,即使它的每个像素都需要透过额外的中间图形缓冲区。
❞未来变化
在目前 master 的#31198 这个合并上,提出了捷伊同时实现方式用于替代现有的 Virtual Display 。
这个还未发布到正式本的调整上, Hybrid Composition 基本没有变化,主要是调整了一些命名,主要逻辑还是是 createForTextureLayer ,目前还无法保证它后续的进展,目前还有 一部分进度在#97628 ,因此先简单如是说下它的情况。
在这个捷伊同时实现上,Virtual Display 的逻辑变成了 PlatformViewWrapper , PlatformViewWrapper 本身是两个 FrameLayout ,同样是 flutterView.addView(); ,基本逻辑和 Hybrid Composition 很像,只不过现在加进的是 PlatformViewWrapper 。
image-20220309163231386在这里 Virtual Display 没有了,原本 Virtual Display 创建的 Surface 被设置到 PlatformViewWrapper 里面。
简单如是说下:**在 PlatformViewWrapper 里,会透过 s
因此 child 了绘制切换而无需使用 VirtualDisplay 。
当然,目前在测试中接收到的反馈里有还不如以前的操控性好,因此后续会如何调整还是需要看测试结果。
❝PS ,如果这个修改正式发布,可能 Flutter 的 Android miniSDK 版本就需要到 23 起步了。即使 lockHardwareCanvas() 需要 23 起,而不用兼容更低平台的原因是 lockCanvas() 属于 CPU copy ,操控性上会慢很多
❞