Flutter AndroidPlatformView

https://guoshuyu.cn/home/wx/Flutter-P3.html
https://juejin.cn/post/7093858055439253534#heading-8

首先看下 Android 端 PlatformView 创建过程。(以下分析基于flutter 3.10版本实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// PlatformView 定义
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView

override fun getView(): View {
return textView
}

override fun dispose() {}

init {
textView = TextView(context)
textView.textSize = 72f
textView.setBackgroundColor(Color.rgb(255, 255, 255))
textView.text = "Rendered on a native Android view (id: $id)"
}
}
// 创建 factory
class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return NativeView(context, viewId, creationParams)
}
}

// 注册 PlatformView
class PlatformViewPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
binding
.platformViewRegistry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}

override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}

flutter端调用有两种方式,一种是 PlatformViewLink(对应native 端实现是Hybrid composition,后面讲):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = '<platform-view-type>';
// Pass parameters to the platform side.
const Map<String, dynamic> creationParams = <String, dynamic>{};

return PlatformViewLink(
viewType: viewType,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
// 这里可以用 PlatformViewsService.initSurfaceAndroidView
// 也可以用 PlatformViewsService.initAndroidView
// 也可以用 PlatformViewsService.initExpensiveAndroidView
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}

另一种是 AndroidView(对应native 端实现是Virtual display,后面讲):

1
2
3
4
5
6
7
8
9
10
11
12
13
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = '<platform-view-type>';
// Pass parameters to the platform side.
final Map<String, dynamic> creationParams = <String, dynamic>{};

return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}

我们知道 flutter 和 native 交互都是通过 channel 进行,PlatformView 创建也不例外。创建 PlatformView 首先会调用 PlatformViewsChannel 类中 parsingHandler对象的 create 方法。create 方法中会根据 hybrid 参数为 true 时调用 PlatformViewsController.createForPlatformViewLayerflase 时调用 PlatformViewsController.createForTextureLayer,这两个方法里会决定 PlatformView 的实现方式。

先看下hybrid参数是哪里来的。Flutter 端创建的时候不管是 AndroidView 方式,还是 PlatformViewLink 方式,都会创建一个 AndroidViewController 对象,AndroidViewController 对象负责通过 channel 和 native 端通信。对象,AndroidViewController 有三个实现,分别是 TextureAndroidViewController、SurfaceAndroidViewController、ExpensiveAndroidViewController。TextureAndroidViewController 和 SurfaceAndroidViewController 中 hybrid 值为 false,ExpensiveAndroidViewController 中 hybrid 值为 true

回到 PlatformViewsController 类。createForPlatformViewLayer() 方法很明显,采用 HybridComposition 实现。createForTextureLayer() 方法并不一定采用 TextureLayer 实现(因为 TextureLayer 需要依赖硬件加速,所以sdk版本需要23级以上,而且 TextureLayer 不支持 SurfaceView,所以如果 PlatformView 中包含 SurfaceView 也无法使用 TextureLayer 实现)。以下为createForPlatformViewLayer方法中的注释:

1
2
3
4
5
6
// The newer Texture Layer Hybrid Composition mode isn't suppported if any of the
// following are true:
// - The embedded view contains any of the VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY view types.
// These views allow out-of-band graphics operations that aren't notified to the Android
// view hierarchy via callbacks such as ViewParent#onDescendantInvalidated().
// - The API level is <23, due to TLHC implementation API requirements.

所以如果不支持 TextureLayer 实现的话会降级为 HybirdComposition 实现或 VirtualDisplay 实现。具体降级为哪个实现是根据创建是的 hybridFallback 参数来确定。
SurfaceAndroidViewController 的 hybridFallback 参数为 true,如果不支持 TextureLayer 会降级为 HybirdComposition 实现。

总结:

TextureLayer实现

  • sdk 版本为 23 及以上,并且 NativeView 中不含有 SurfaceView,flutter 端通过 AndroidView 调用,或者通过 PlatformViewLink 调用并且onCreatePlatformView方法中返回TextureAndroidViewController 或 SurfaceAndroidViewController

HybirdComposition

  • sdk 版本为 23 以下,或 NativeView 中含有 SurfaceView,flutter 端通过 PlatformViewLink 调用并且onCreatePlatformView方法中返回 SurfaceAndroidViewController
  • flutter 通过 PlatformViewLink 调用并且onCreatePlatformView方法中返回ExpensiveAndroidViewController

VirtualDisplay实现

  • sdk 版本为 23 以下,或 NativeView 中含有 SurfaceView,flutter 端通过 AndroidView 调用,或者通过 PlatformViewLink 调用并且onCreatePlatformView方法中返回TextureAndroidViewController 或 SurfaceAndroidViewController

Flutter AndroidPlatformView 最早是采用 VirtualDisplay 实现的。VirtualDisplay 实现大致原理是创建一块虚拟屏幕,把 AndroidView 绘制到虚拟屏幕上,

VirtualDisplay实现:

VD简单来说就是使用 VirtualDisplay 渲染原生控件到内存,然后利用 id 在 Flutter 界面上占用一个相应大小的位置,最后通过 id 关联到 Flutter Texture 里进行渲染。
问题:控件不是被真实渲染,容易有触摸和键盘等问题

HybirdComposition实现:

1.2 版本开始支持 HC,简单说就是直接把原生控件覆盖在 Flutter 上进行堆叠,如果出现 Flutter Widget 需要渲染在 Native Widget 上,就采用新的 FlutterImageView 来承载新图层。好处是原生视图是直接显示渲染,坏处就是在 Android 10 之前存在 GPU->CPU->GPU的性能损耗。
另外因为此时原生控件是直接渲染,所以需要在原生的平台线程上执行,这和 Flutter 的 UI 线程就存在线程同步问题,所以在此之前一些场景下会有画面闪烁 bug 。
问题:直接堆叠控件,会有性能开销和线程同步问题,某些场景容易出现闪烁和卡顿

TextureLayer实现:
3.0 版本开始支持 TLHC 模式,最初的目的是取代上面这两种模式,奈何最终只能共存下来,该模式下控件虽然在还是布局在该有的位置上,但是其实是通过一个 FrameLayout 代理 onDraw 然后替换掉 child 原生控件的 Canvas 来实现混合绘制。
但是这种实现天然不支持 SurfaceView ,因为 SurfaceView 是双缓冲机制,所以通过 parent 替换 Canvas 的实现并不支持。
问题:不支持 SurfaceView ,对于使用 SurfaceView 的播放器、地图等插件会有兼容性问题。

Author

Lyuku

Posted on

2022-10-15

Licensed under