Flutter 3.0 之 PlatformView :告别 VirtualDisplay ,拥抱 TextureLayer

2022-05-24 11:00:29 浏览数 (1)

在 Flutter 3.0 发布之前,我们通过 《Flutter 深入探索混合开发的技术演进》 盘点了 Flutter 混合开发的历史进程, 在里面就提及了第一代 PlatformView 的实现 VirtualDisplay 即将被移除,而随着最近 Flutter 3.0 的发布,这个变更正式在稳定版中如期而至,「所以今天就详细分析一下,新的 TextureLayer 如何替代 PlatformView」

首先,如下图所示,简单对比 VirtualDisplayTextureLayer 的实现差异,「可以看到主要还是在于原生控件纹理的提取方式上」

image-20220516154120729

从上图我们可以得知:

  • VirtualDisplayTextureLayer「Plugin 的实现是可以无缝切换,因为主要修改的地方在于底层对于纹理的提取和渲染逻辑」
  • 以前 Flutter 中会将 AndroidView 需要渲染的内容绘制到 VirtualDisplays ,然后在 VirtualDisplay 对应的内存中,绘制的画面就可以通过其 Surface 获取得到;「现在AndroidView需要的内容,会通过 View 的draw方法被绘制到SurfaceTexture里,然后同样通过TextureId获取绘制在内存的纹理」

是不是有点懵?简单地说,如下图所示:

  • 现在 PlatformViewsController 在加载 PlatformView 时, 在 createForTextureLayer方法里会先创建一个 PlatformViewWrapper 对象,然后返回一个 TextureId 给 Dart ;
  • PlatformViewWrapper 本身是一个 Android 的 FrameLayout ,主要作用就是:通过 addView添加原生控件,然后「在draw方法里通过super.draw(surfaceCanvas);将 Android View 的 Canvas 替换成PlatformView创建的SurfaceTexture的 Canvas」
  • 在 Dart 层面, AndroidView 通过 TextureId 告诉 Engine 需要渲染的纹理信息,Engine 提取出前面 super.draw(surfaceCanvas); 所绘制的纹理,并渲染出来;

image-20220516163607760

「这里面的关键就在于 super.draw(surfaceCanvas);

PlatformView 创建时,Flutter 会为其创建一个SurfaceTexture 用于生成 Surface,相当于是在内存里新建了一个画板。

PlatformViewWrapper 里通过 surface.lockHardwareCanvas()获取到了这个画板的 Canvas ,也就是 surfaceCanvas ,相当于是画笔 。

接着 Flutter 通过 overridePlatformViewWrapperdraw(Canvas canvas)方法,然后在 super.draw 时把默认 View 的 Canvas 替换为上面的 surfaceCanvas

比如这时候我们需要渲染的原生控件是 TextView「因为此时 TextView PlatformViewWrapper 的子控件,所以当它绘制时,使用的画笔就会是 surfaceCanvas ,而它的界面效果就会被绘制到对应 Id 的 SurfaceTexture 里」

所以在新流程里,原生控件同样是渲染到内存,然后通过 Id 去获取纹理数据,但是对比 VirtualDisplay 它更直接,因为是直接位置到内存纹理而不是通过虚显,并且这里有个关键内容:

「使用的是 lockHardwareCanvas() 而不是 lockCanvas() lockHardwareCanvas()需要 API 23 以上才支持,因为它支持硬件加速,而不是像 lockCanvas 一样需要频繁的 CPU 拷贝,从而提高了性能。」

那我们知道,在以前的 VirtualDisplays 实现里,除了性能问题,还有控件的触摸问题,因为 AndroidView 其实是被渲染在 VirtualDisplay 中 ,而每当用户点击看到的 "AndroidView"时,其实他们就真正”点击的是正在渲染的 Flutter 纹理 ,用户产生的触摸事件是直接发送到 Flutter View 中,而不是他们实际点击的 AndroidView

而在 TextureLayer 的实现里,「虽然控件同样是被绘制到内存,但是 PlatformViewWrapper是真实存在布局里的」

什么意思呢?

如下图所示,是将两个 TextView 通过 TextureLayer 的方式添加到 Flutter 里 ,然后我们通过 Android Studio 的 Layout Inspector 查看,可以看到 FlutterView 下会有两个 PlatformViewWrapper ,并且它们都有一个 TextView 的子控件。

image-20220516112123711

「此时因为 TextView 的子控件的 Canvas 被 Flutter 给替换了,所以在画面上看不到渲染内容,但是它们所在的位置依然可以接受点击事件」

所以在 PlatformViewWrapper 中,它 override 了 onTouchEvent 方法,并且将对应的 MotionEvent 进行封装,然后分发到 Flutter 的 Dart 层进行处理。

❝当然,此时 PlatformViewWrapper 的位置和大小 ,是通过 Dart 层的 AndroidView 传递过来的信息进行定位,而 PlatformViewWrapper 的位置其实和渲染效果没有关系,即使 PlatformViewWrapper 不在正常位置,画面也可以正常渲染,它影响的主要还是触摸事件的相关逻辑。 ❞

值得注意的是, PlatformViewWrapper 里的 onInterceptTouchEvent 返回了 true ,也就是触摸事件会被它拦截,而不会传递到父控件,避免了 FlutterView 收到干扰」

image-20220516172819574

另外 PlatformViewWrapper 还提供了焦点相关的处理逻辑,通过接口将焦点的变化状态返回给 Dart 层。

image-20220516173618441

最后, PlatformViewWrapper 里还有一个小兼容处理:就是在 Android Q 上 SurfaceTexture需要绘制完上一帧之后,才能绘制下一帧。

image-20220516174428087

简单地说,具体流程为:

  • 所以当 Engine 每次绘制时,就会触发 onFrameComsumed 去对 pendingFramesCount 进行 -1 操作;
  • 每次有新的 SurfaceTexture 或者 draw(Canvas canvas) 调用,就对 pendingFramesCount 进行 1 操作;

通过 pendingFramesCount 的计数方式,当 pendingFramesCount.get() <= 0L 才进行 Surface 绘制,保证了 Android Q 上 SurfaceTexture 每次提交绘制都是最后一帧的画面。

可以看到 ,新的 TextureLayer 实现更简单直接,实现了在性能提高的同时,简化了实现的复杂度,同时也弥补了 VirtualDisplay 的一些缺陷。

最后,从 Flutter 3.0 源码上看,「社区有打算移除 HybirdComposition 的计划,但是这无疑是一个涉及面比较大的 break change ,最终是否能够通过还不得而知」,而从我个人角度出发,我是觉得 HybirdComposition 在某些场景还有存在的必要,如果想详细了解 HybirdComposition,可以参考 《Flutter 深入探索混合开发的技术演进》

image-20220516180731371

0 人点赞