Flutter 在鸿蒙上跑原生视图,终于不用烤成一张皮了

0 阅读6分钟

OHOS Flutter HCPP 技术解读

Flutter 在 OpenHarmony 上嵌入原生视图,一直有个老毛病。原生内容得先烤成一张纹理,再贴回 Flutter 的图层树里。纹理采样一过,高光丢一点,圆角糊一点,透明度叠上去画面就肉。性能也白白搭进去一层采样开销。

Flutter 3.44 在 OpenHarmony 上多了个东西叫 HCPP,全称 Hybrid Composition Plus Plus。名字长,但干的事情一句话能说明白:让原生视图别再被压成纹理皮了,直接交给系统合成器叠上去。

最近把这个技术的架构和实测数据过了一遍,挺有意思,跟你聊聊。

把原生视图,交还给系统合成器

HCPP 不是新控件,是换了一条上屏的路。

以前的做法叫纹理路径。原生视图的像素进 Flutter 的 GL 或 Vulkan 纹理管线,被 ExternalTexture 采样后,塞进 Flutter 主 surface 合成上屏。说人话就是,原生内容先变成一张皮,再贴回去。

HCPP 的做法是绕过这层管线。原生视图不进 Flutter 纹理了,作为 ArkUI 的 DISPLAY 节点,由系统的 RenderService 直接合成上屏。原生内容就待在系统层,Flutter 只管画自己那部分,两个层由系统叠在一起。

默认是关的。不开的时候,行为跟原来的纹理路径完全一样,不用担心兼容性。

三层拆开讲:Link / Embedder / ArkTS

HCPP 不是一坨代码,是三层各干各的,拼在一起才跑得通。一层一层说。

第一层:PlatformViewLink 选路

这是 Dart 框架层的事。PlatformViewLink 是原生 PlatformView 和 Flutter 框架之间的手脚架,分配 viewId、创建控制器、挂上 OhosViewSurface,把布局、手势、焦点、生命周期都接好。

真正分叉的地方在 onCreatePlatformView 回调。选哪条 init 方法,就代表走哪条路。一共有三种。

surfaceFallback,日常推荐。能切就切。HCPP 开关开了走系统合成,关了走纹理。Dart 侧自动判断 requiresViewComposition 条件,动态选择。最灵活,也是默认推荐的路。

texture,强制纹理。不管你开没开 HCPP,原生都先烤成纹理再塞进去。适合那些确认不需要 HCPP 的场景。

expensiveHybrid,强制 HCPP。直接要求系统合成,必须先打开 HCPP 开关,不然走不通。

日常开发记住一个就够了:surfaceFallback。能走系统合成就走,走不了自动退回纹理,不用你操心。

第二层:Embedder 每帧干五件事

这一层是 C++ 引擎侧的活。OHOSExternalViewEmbedder 在每帧渲染时做五件事,按顺序来。

门禁。flag 和 GPU 条件(Vulkan 或 GLES)同时满足才创建 HCPP 相关对象。缺一个就静默退回纹理路径,不会崩。

记账。Preroll 阶段记下每个平台视图的层级关系、包围盒、mutator 变换链,本帧合成时要用。

挖洞。SliceViews 在主帧背景上按原生视图的矩形区域打洞,把那块位置让出来,让原生 DISPLAY 从洞里露出去。

切片。盖在原生视图上面的 Flutter 控件,画进 overlay surface,形成顶层图层。

下发。通过 NAPI 把原生视图的几何布局信息和 overlay rect 中 Flutter UI 的布局信息交给 ArkTS 层。

没有 Embedder 这层,PlatformViewLayer 在引擎里就无处落地,Dart 侧再怎么选 Hybrid 模式也合成不了系统层。

第三层:ArkTS 嵌入层摆位

这一层负责把原生视图真正做成系统层。PlatformViewsControllerHybrid 是 HCPP 专用的控制器,按 hybrid 或 hybridFallback 模式加上设备能力判定来路由。成功返回 null,表示不走外接纹理;失败返回 textureId,退回纹理模式。

页面结构是一个三明治。

底层是 Flutter 背景。平台视图区域的洞已经挖好了,主表面必须透明,不然洞露不出去。

中层是原生 DISPLAY。原生内容本身,由系统 RenderService 直接合成,不走外接纹理。

顶层是 Flutter Overlay。盖在原生上面的 Flutter 控件,画在 overlay surface 里。

MutatorView 每帧套上原生视图的位置、裁剪、旋转、缩放、透明度。引擎把几何信息下发下来,嵌入层在这里算转换矩阵、设摆位。上屏时按底中顶的顺序,由 RenderService 叠在一起。

CPU 指令数,最高少了三成

架构说了这么多,到底省了多少。拿实测数据说话,指标是 CPU 指令数,越低越好。

场景纹理模式HCPP降幅
HDR 场景30.6B20.5B-32%
WebView 滑动加载67.2B58.4B-13%
WebView 桌球动画52.0B46.8B-10%

原始性能数据截图,三个场景的指令数逐项对比

HCPP渲染模式负载收益原数据截图转存失败,建议直接上传图片文件

HDR 场景降得最猛,三成。WebView 两个场景分别少了一成多。省下来的指令主要在应用进程和 RenderService 进程里。原始数据截图里能看到,HDR 场景应用进程的指令数从 36.8 亿降到 1.57 亿,render_service 从 56 亿降到 21.8 亿,降幅都很夸张。

HDR 场景收益最大,原因是高光、圆角、透明度叠加这类操作在纹理路径下开销特别大。HCPP 让原生直接待在系统层,这些操作由合成器原生处理,不用再走纹理采样那一圈。

同一帧植物,两种合成

性能数字是一方面,眼睛是另一方面。拿同一帧植物视频做对比,左边纹理模式,右边 HCPP。

纹理模式(左)vs HCPP(右)同一帧画面对比

纹理模式 HCPP

左边把原生先烤成一张皮,再塞进 Flutter 图层树。纹理采样会丢一些信息,叶脉和高光的细节会糊。

右边让原生待在系统显示层,由 RenderService 直接叠。没有纹理采样的中间环节,细节保真度更高。看叶面的高光区域和叶脉边缘,差别比较明显。

静态看可能差别不大。但交叉遮挡、圆角、透明度一上场,差距会明显起来。

这也是为什么 WebView 和视频播放场景特别吃这套,它们本身就有大量这类视觉效果。

怎么开:两道门槛,两种方式

开 HCPP 有两个条件必须同时满足。开关设为 true,并且 GPU 支持 Vulkan 或 GLES。两个都满足才会创建 HCPP Embedder,缺一个就静默退回纹理。

方式 A:命令行(开发调试用)

flutter run --enable-hcpp

方式 B:配置文件(正式包推荐)

在 ohos/entry 目录下的 rawfile/buildinfo.json5 里写入资源键:

"enable_ohos_hybrid_composition": {
  "value": "true"
}

两路最终都落到同一个资源键,效果一样。正式打包用配置文件,开发调试用命令行,各取所需。

最后

HCPP 这套东西,说到底就是让原生视图待在它该待的那一层。

以前为了在 Flutter 里嵌原生,得把原生压成纹理再贴回去,有开销,有画质损失。现在开了开关、用 Hybrid 模式,合成交给 RenderService,原生就在系统层自己叠好了。

架构上三层各司其职。Dart 侧 PlatformViewLink 选路,C++ Embedder 每帧挖洞切片下发,ArkTS 嵌入层摆位合成。日常开发用 surfaceFallback 就够了。

性能上 HDR 场景 CPU 指令能省三成,WebView 场景也有一成多的收益。视觉上纹理采样的中间环节没了,高光和细节保真度更高。

开开关,用 Hybrid,把合成交给系统。


基于 Flutter 3.44 OpenHarmony · OHOS HCPP 技术分享