Fluttter 混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进

对于使用过 Flutter 的开发来说,应该对在 Flutter 混合开发中,通过 PlatformView 接入原生控件的方式并不陌生,而如果你是从 Flutter 1.20 之前就开始使用 Flutter ,那么应该对于 Android 上 PlatformView 的各种体验问题有过深刻的体会,比如: WebView 里弹出键盘的问题

⚠️注意:文末有惊喜

从一个问题开始

恰巧最近一位朋友在 Flutter 2.10.1 上使用 webview_flutterflutter_pdfview 测试时出现了如下的问题:

attachToContext: GLConsumer is already attached to a context at android.graphics.SurfaceTexture.attachToGLContext(SurfaceTexture.java:289)

所以借着这个问题来给大家科普下 Flutter 里 PlatformView 实现的变迁和未来调整,首先这个问题的起因是因为:

virtual displayes 和 hybrid composition 两种 PlatformView实现混合使用。

因为从 Flutter 2.10 开始,官方的 Plugin 如 webview_flutter 默都是使用 hybrid composition 的实现,而第三方的 flutter_pdfview 目前还是使用以前的 virtual display ,这就出现了两种 PlatformView 实现同时出现的情况。

当然,官方在 2.10.2 版本的 #31390 上修复了这个问题, 问题的原因在于:当 rasterizer 任务运行不同的线程时,GrContext 会被重新创建,从而导致 texture 变成没有初始化的状态,进而重复调用 attachToGLContext 导致崩溃

所以后续官方修复这个问题,就是在 attachToGLContext 之前,如果 texture 已经 attach 过,就先调用 detachFromGLContext 进行释放,从而避免了初始化 context 的问题。

但是从问题上看,其实这个问题并不是 2.10 才会出现,而是只要在 SurfaceTextureWrapper 这个对象存在时 ,混合使用 virtual displayeshybrid composition 就能引发这个 bug 。

SurfaceTextureWrapper 是官方用于处理同步的问题,因为当 SurfaceTexture 被释放时,由于 SurfaceTexture.release 是在 platform 线程被调用,而 attachToGLContext 是在 raster 线程被调用,不同线程调用时可能导致:attachToGLContext 被调用时 texture 已经被释放了,所以需要 SurfaceTextureWrapper 用于实现 Java 里同步锁的效果

所以如果在低版本不想升级,那么可以选择所有 Plugin 都使用 virtual display 模式或者 hybrid composition 模式,比如 webview_flutter 就提供了 WebView.platform 用于用户自由选择 PlatformView 的渲染模式。

当然一般情况下我是更建议大家目前都使用 hybrid composition 模式,虽然两种模式都有潜在问题,但是相比起来目前 virtual display 带来的性能和键盘问题会让人更难以接受

区别和演进

其实在之前的 《 Hybrid Composition 深度解析》 里就介绍过它们实现的区别,这里再结合上面的问题,从不一样的角度介绍下它们的实现差异和变迁。

VirtualDisplay

一般 dart 代码里直接使用 AndroidView 的我们就可以简单认为是使用 virtual display ,比如 flutter_pdfview 1.2.2 版本 , 这种实现方式是 通过将 AndroidView 需要渲染的内容绘制到 VirtualDisplays 实现中 ,然后在 VirtualDisplay 对应的内存里,绘制的画面就可以通过其 Surface 获取得到

image

VirtualDisplay 类似于一个虚拟显示区域,需要结合 DisplayManager 一起调用,一般在副屏显示或者录屏场景下会用到。VirtualDisplay 会将虚拟显示区域的内容渲染在一个 Surface上。

如上图所示,简单来说就是原生控件的内容被绘制到内存里,然后 Flutter Engine 通过相对应的 textureId 就可以获取到控件的渲染数据并显示出来

关于 virtual display 实现,如果你需要对应路径去调试问题,可以参看如下流程:

image-20220305161230961

HybridComposition

使用 hybrid composition 相对会比直接使用 AndroidView 在代码上更复杂一点, 需要使用到 PlatformViewLinkAndroidViewSurfacePlatformViewsService 这三个对象,首先我们要创建一个 dart 控件:

  • 通过 PlatformViewLinkviewType 注册了一个和原生层对应的注册名称,这和之前的 PlatformView 注册一样;
  • 然后在 surfaceFactory 返回一个 AndroidViewSurface 用于处理绘制和接收触摸事件;
  • 最后在 onCreatePlatformView 方法使用 PlatformViewsService 初始化 AndroidViewSurface 和初始化所需要的参数,同时通过 Engine 去触发原生层的显示。
Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  final String viewType = 'hybrid-view-type';
  // Pass parameters to the platform side.
  final Map<String, dynamic> creationParams = <String, dynamic>{};

  return PlatformViewLink(
    viewType: viewType, 
    surfaceFactory:
        (BuildContext context, PlatformViewController controller) {
      return AndroidViewSurface(
        controller: controller,
        gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: (PlatformViewCreationParams params) {
      return PlatformViewsService.initSurfaceAndroidView(
        id: params.id,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: StandardMessageCodec(),
      )
        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
        ..create();
    },
  );
}

如果通过上面的问题来做个直观的对比,就会是如下图所示的变化:

image-20220305160606360

使用 hybrid composition 之后, PlatformView 是通过 FlutterMutatorView 把原生控件 addViewFlutterView 上,然后再通过 FlutterImageView 的能力去实现图层的混合,简单解释就是:

Flutter 只直接通过原生的 addView 方法将 PlatformView 添加到 FlutterView ,这就不需要什么 surface 渲染再去获取的开销,而当你还需要再 PlatformView 上渲染 Flutter 自己的 Widget 时,Flutter 就会通过再叠加一个 FlutterImageView 来承载这个 Widget 。

img

举个例子,如下图所示,其中:

  • 两个灰色的 Re 是原生的 TextView;

  • 蓝色、黄色、红色的是 Flutter 的 Text

img

从渲染结果上可以看到:

  • 灰色的原生 TextView 通过 PlatformView 直接就通过原生的 addView 方法添加到 FlutterView 上;
  • 而红色的 Flutter 的 Text 控件因为和 PlatformView没交集,所以还是 Flutter 原本的渲染逻辑;
  • 黄色和蓝色的 Flutter 控件,因为和 PlatformView 有交集,所以通过新的 FlutterImageView 做承载渲染。

使用 hybrid composition 后,在 Engine 去 SubmitFrame 时,会通过 current_frame_view_count 去对每个 view 画面进行规划处理,然后会通过判定区域内是否需要 CreateSurfaceIfNeeded 函数,最终触发原生的 createOverlaySurface 方法去创建 FlutterImageView

    for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
      std::unique_ptr<SurfaceFrame> frame =
          CreateSurfaceIfNeeded(context,               //
                                view_id,               //
                                pictures.at(view_id),  //
                                overlay_rect           //
          );
      if (should_submit_current_frame) {
        frame->Submit();
      }
    }

如果有需要调试 hybrid composition 相关功能的,可以参考如下路径, 和 virtual display 不同之处就是在 create 之后的路径产生了变化 , 更多详细演示可见:https://juejin.cn/post/6858473695939084295#heading-2

image-20220305165318255
image-20220305141848256

结论

所以可以看到,hybrid composition 保留了更多的原生控件效果,也节省了渲染成本 ,当然目前 PlatformView 还有一个比较尖锐的问题,例如 #95343 的闪动问题,这个问题看来在未来会通过更改渲染方式和纹理优化来解决。

是的,还是因为性能等问题,所以新的 PlatforView 实现来又要来了,从上面提到的 #31198 已经合并可以猜测,下一个稳定版本中,现在的 virtual displayes 实现将不复存在,进而替代的是通过新的 TextureLayer 实现,未来不排除 hybrid composition 也会被取消,不知道大家此刻心情如何?

image-20220305170157117

简单说就是:

  • 新的 PlatformViewWrapper 会替换掉原本 virtual displaySurfaceTextureWrapper 相关的逻辑,通过对输入的 Surface 进行 lockHardwareCanvas 获取到 Canvas ,再通过 super.draw(surfaceCanvas); 进行绘制;
  • 关于 hybrid composition 目前看起里仅是更换了称谓,只要核心逻辑没有大变动;

而如果未来 PlatformViewWrapper 的实现效果良好 ,可以猜测 hybrid composition 模式也会进而退出历史舞台,所以唯有感慨, Flutter 的技术演进速度真的好快。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容