Flutter 正在推进全新 PlatformView 实现 HCPP, 它又用到了 Android 上的什么黑科技

跨平台开发里的 PlatformView 实现一直是一个经久不衰的话题,在之前的 《深入 Flutter 和 Compose 的 PlatformView 实现对比》 我们就详细聊过 Flutter 和 Compose 在 PlatformView 实现上的异同之处,也聊到了 Compose 为什么在相同实现上对比 Flutter 会更有优势的原因。

那么随着 3.29 的发布,恰好关注到其实 Flutter 在 Android 的 PlatformView 上其实正在落地另外一种实现,而这种实现目前看来可以做到在 HC 的基础上得到更好的性能,所以也被暂时称为 HCPP。

在聊 HCPP 之前我们再简单回顾下 Flutter 在 Android 上的 PlatformView 实现模式:

  • VD:最老的实现模式,利用副屏 VirtualDisplay 的相关支持在内存实现原生控件的模拟绘制和纹理提取
  • HC:通过直接将原生控件 add 到 FlutterView 上,然后通过新的 FlutterImageView 提供新的 Surface 来实现控件 UI 堆叠合成
  • TLHC:还是直接将原生控件 add 到 FlutterView 上,但是中间利用通过 parent 替代掉 child 的 canvas,让原生控件绘制的对应的 surface.lockHardwareCanvas

有点抽象?,没关系,后面有简单的直观例子。

在这个过程中几种模式各有优劣,比如:

  • TLHC 模式不支持 SurfaceView 等控件,因为 SurfaceView 有自己独立的 Surface 和 Canva ,它的 Surface 直接来自 SurfaceFlinger ,也就是当前 Window 下
  • TLHC 模式与异步更新 View 一起使用时(如 TextureView 或基于 GL 的渲染器),需要在更新内容时在对应 PlatformView 显式调用 invalidate 才能保证正确渲染,例如 textureViewonSurfaceTextureUpdated 调用 mapView.invalidate
  • HC 模式因为不同 API 版本和线程问题,会有同步和额外的性能开销
  • ····

所以目前这三种模式是协同工作,例如:

  • initAndroidView : 默认会使用最新的模式,目前就是 TLHC,如果遇到不支持的就降级到 VD

  • initSurfaceAndroidView: 默认会使用最新的模,目前就是 TLHC,如果遇到不支持的就降级到 HC

  • initExpensiveAndroidView:直接强制使用 HC 模式

那么,回到本次的主题 ,针对全新的 HCPP 实现,Flutter 提供了全新的 API initHybridAndroidView ,可以看到,它需要 Vulkan 和 API 34 的环境才支持使用,如果从这点看,它的通用性又相对较低

那为什么它需要 API 34 呢?这和它直接使用 SurfaceControl 的 API 逻辑有很大关系 ,另外,从 Engine 的判断逻辑上可以看到,目前除了判断 Vulkan 和 API 之后,还需要配置对应的 EnableSurfaceControl 才可以测试 HCPP,也就是在 AndroidManifest 增加:

xml 复制代码
<meta-data
       android:name="io.flutter.embedding.android.EnableSurfaceControl"
       android:value="true" />

接着就让我们来看看 HCPP 和其他几种模式有什么区别,其实主要就是和 HC 和 TLHC 进行比较,这里首先做一个容器 Demo ,主要是通过混合 Flutter 和原生控件的效果来区分它们的实现,让 platformView 渲染在两个 Flutter Widget 之间:

dart 复制代码
return MaterialApp(
  debugShowCheckedModeBanner: false,
  home: Stack(
    alignment: AlignmentDirectional.center,
    children: <Widget>[
      ///200x200的绿色 Flutter 方块
      TextButton(
        key: const ValueKey<String>('AddOverlay'),
        onPressed: _togglePlatformView,
        child: const SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)),
      ),
      
      ///200x200的原生控件,这里用的是一个红色的原生方块
      if (showPlatformView) ...<Widget>[
        SizedBox(width: 200, height: 200, child: widget.platformView),
        
        
        ///黄色 Flutter 条
        TextButton(
          key: const ValueKey<String>('RemoveOverlay'),
          onPressed: _togglePlatformView,
          child: const SizedBox(
            width: 800,
            height: 25,
            child: ColoredBox(color: Colors.yellow),
          ),
        ),
      ],
    ],
  ),
);

之后我们可以通过 initExpensiveAndroidView 强制 PlatformView 使用 HC 模式,可以看到,在 HC 模式下出现很多经典的原生层,特别是多了 FlutterImageView 的转换还有它的子类 PlatformOverlayView

我们通过 3D 图可以看到,红色的原生 BoxPlatformView 正常被渲染,然后在其之上的 Flutter 控件(一部分黄色条),是通过 FlutterImageView 的子类 PlatformOverlayView 提供的 Surface 独立渲染:

然后我们再通过 initAndroidView 来使用 TLHC 模式,可以看到此时是通过 PlatformViewWrapper 这个 parent 作为容器来承载,而 PlatformViewWrapper 会替换掉原生 BoxPlatformView 的 Canvas,让原生控件的内容渲染到指定 Surface 上 :

我们通过原生 3D 图可以看到,此时的 BoxPlatformView 其实在原生层并没有绘制任何东西,因为其 Canvas 是被替换到内存的 SurfaceTexture 上:

说到 SurfaceTexture ,这个插个题外话,对于 THLC 和 VD 而言,现在创建纹理时是会根据 Android API 来使用不同实现,其中 SurfaceProducer 比较特殊:

因为在此之前,Android 上的 Flutter 引擎支持两个外部渲染源:SurfaceTexture (OpenGLES 纹理)和 ImageReader(GPU-ready buffer),其中 Image.getHardwareBuffer 需要 API 28 支持。

而为了适配 Impeller 团队提出了 SurfaceProducer 概念,让 Android 在运行时选择"最佳"渲染 Surface,除了 PlatformView 场景,在外界纹理场景也需要适配的情况:

diff 复制代码
- TextureRegistry.SurfaceTextureEntry entry = textureRegistry.createSurfaceTexture();
+ TextureRegistry.SurfaceProducer producer = textureRegistry.createSurfaceProducer();

- Surface surface = new Surface(entry.surfaceTexture());
+ Surface surface = producer.getSurface();

那么我们看 HCPP,通过 initHybridAndroidView 我们启用了 HCPP 模式,可以看到,此时 UI 的层级结构类似 TLHC, 但是 parent 使用的是 HC 模式中的 FlutterMutatorView

然后我们看 3D 效果,原生控件 BoxPlatformView 其实可以被完整被渲染,证明其 Canvas 并没有被替代,那么这里就有一个神奇的问题了:Flutter 的黄色控件,是如何渲染到红色的 BoxPlatformView 之上的

这就不得不提 PlatformViewsController2 ,作为一个 HCPP 的临时对象,它的实现里有一个关键的对象 SurfaceControl ,并且在事务提交时通过 setLayer 设置了 z 轴为 1000

我们可以看提交更改里,基本上全新的 PlatformViewsController2 核心逻辑都在于操作 SurfaceControl

在 Android 里,SurfaceControl 是一种用于管理和操作与显示系统相关的图形资源的类,简单说就是与 Surface 相关的操作,SurfaceControl 可以用于创建和管理 Surface ,它是和SurfaceFlinger 交互的一个主要接口,交互的方式则是通过 Transaction

而在 HCPP 里,我们可以看到,此时的 Surface 正是通过一个全新的 SurfaceControl 创建得到,而这个 SurfaceControlTransaction 来自 FlutterView

也就是,在 HCPP 模式里,Flutter 通过 SurfaceControl.Transaction 构造了一个全新的 Surface 用于 SurfaceFlinger 合成,并且还通过 setLayer 将 Surface 的 z 轴设置到了 1000 ,而这个 1000 就是黄色 Flutter 控件可以渲染到原生红色方块之上的原因

举个例子,我们将 SurfaceControl 这部分代码复制到一个简单的纯原生项目里,并且同样对创建的 Surface 设置 1000 和绘制红色:

java 复制代码
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
  
    	 ········
   
       // 获取 FrameLayout
        FrameLayout rootView = findViewById(R.id.container);
        rootView.postDelayed(new Runnable() {
            @Override
            public void run() {
                final SurfaceControl.Builder surfaceControlBuilder = new SurfaceControl.Builder();
                surfaceControlBuilder.setBufferSize(500, 500);
                surfaceControlBuilder.setFormat(PixelFormat.RGBA_8888);
                surfaceControlBuilder.setName("Flutter Overlay Surface");
                surfaceControlBuilder.setOpaque(false);
                surfaceControlBuilder.setHidden(false);
                final SurfaceControl surfaceControl = surfaceControlBuilder.build();
                final SurfaceControl.Transaction tx =
                        binding.container.getRootSurfaceControl().buildReparentTransaction(surfaceControl);
                tx.setLayer(surfaceControl, 1000);
                tx.apply();
                surface1 = new Surface(surfaceControl);
                surfaceControl1 = surfaceControl;

                // 在 SurfaceView 上绘制一些内容
                drawOnSurface(surface1, Color.RED);   
            }
        }, 2000);

}

private void drawOnSurface(Surface surface, int color) {
    Canvas canvas = surface.lockCanvas(null);
    if (canvas != null) {
        canvas.drawColor(color);
        surface.unlockCanvasAndPost(canvas);
    }
}

然后我们看最终绘制的效果,可以看到绿色背景的 FrameLayout 是在 WebView 下方的,但是通过 container.getRootSurfaceControl() 创建的 Surface 因为 z 轴为 1000 的原因,最终红色方块会绘制到 WebView 之上:

另外,在目前逻辑中,Engine 如果判断当前帧如果不存在 PlatformView ,并且上一帧存在 PlatformView,那么就会调用 hideOverlaySurface2 从而直接触发 Java 层面的 platformViewsController2.hideOverlaySurface() ,进而隐藏不需要的 Layer :

c++ 复制代码
  if (!FrameHasPlatformLayers()) {
    frame->Submit();
    // If the previous frame had platform views, hide the overlay surface.
    if (previous_frame_view_count_ > 0) {
      jni_facade_->hideOverlaySurface2();
    }
    jni_facade_->applyTransaction();
    return;
  }

所以可以看到,HCPP 主要就是通过 SurfaceControl 来构造一个高层级的 Surface 从而实现最终绘制时混合覆盖的问题 ,这和我们之前聊 《深入 Flutter 和 Compose 的 PlatformView 实现对比》 里 Compose 可以在 PlatformView 里直接使用 SurfaceView 的道理类似,都是 SurfaceFlinger 合成时的层级操作。

至于为什么需要 API 34, 主要也是 SurfaceControl 对应的一些 API 需要的版本都很高,另外我依稀记得, Android 14 在通过 SurfaceControl 实现低延迟绘图时,可以更好支持 Canvas API 通过硬件加速绘制到 HardwareBuffer :

如果对于 Engine 部份逻辑感兴趣的,也可以看 external_view_embedder_2#SubmitFlutterView 这部分逻辑里如何通过 GetLayer 去创建 FlutterOverlaySurface

目前 HCPP 还处于 main 分之的 beta 状态,如果后续正式落地,那对于 Android PlatformView 实现将会是存在 4 种组合模式,相比较 iOS 端多个 CALayer 的合成模式,Android 的 PlatformView 可以说是一路坎坷至今。

最后,你觉得 HCPP 会成为落地为全新的 PlatformView 支持吗?

PS :io.flutter.embedding.android.EnableSurfaceControl 标识还用于控制 Impeller 内部使用 Vulkan swapchain 或者 Android SurfaceControl (AHB swapchain),在 Android SurfaceControl 模式下,Java 端创建的 Transaction 会链接到 AHB swapchain。

当然, AHBSwapchainVK 交换链实现并非在所有 Android 版本上都可用,一般不支持的话,会回退到 KHR swapchain。

参考链接

相关推荐
太空漫步112 小时前
android社畜模拟器
android
神秘_博士2 小时前
自制AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有iPhone
arm开发·python·物联网·flutter·docker·gitee
陈皮话梅糖@5 小时前
Flutter 网络请求与数据处理:从基础到单例封装
flutter·网络请求
海绵宝宝_5 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子7 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch11 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
汇能感知11 小时前
汇能感知的光谱相机/模块产品有哪些?
经验分享·笔记·科技