[EXP] 鸿蒙真机实测 · Mate 80 Pro / HarmonyOS 7 / HongMeng 1.13 [SRC] OpenHarmony 开源源码锚点 [ARCH] 架构层知识

鸿蒙 UI 组件树与渲染管线真机实测
—— 对齐 iOS 方法论的双平台对比

前作(render-pipeline-compare.pages.dev)从 XNU 源码推演了 iOS/鸿蒙/安卓渲染管线并给出架构判定。本文用鸿蒙真机把这些判定落到测量数据上: 按照之前 iOS 侧的组件树方法论(树 dump / 类型直方图 / 可见性分类 / 双通道交叉验证), 在 Mate 80 Pro 上找到等价测量通道,同时观测到鸿蒙的三棵树——应用进程内的 ArkUI 组件树、 应用进程内的 client RS 节点树、以及 render_service 渲染进程内的 RS 渲染树。

📱 HUAWEI Mate 80 Pro(SGT-AL00B)· 1280×2832 · RAM 11.4G + 12G zswap 🧪 测量页面:系统浏览器首页(信息流,对标 iOS 的 bilibili 首页) 🗓 实测日期:2026-09-01

§0速览结论(TL;DR)

  1. 前作的架构判定被真机逐条实锤:render_service 是独立渲染进程(pid 1788,RSS 168MB,46 线程), RS 渲染树(668 节点)确实构建并持有在渲染进程内——不在应用进程;应用侧只见"组件树 + 命令流"。 用户最初印象中的鸿蒙模型(应用内组件树 → 发给 RenderService → 渲染进程构建渲染树)与实测完全一致。
  2. 鸿蒙能同时观测三棵树,iOS 只能观测一棵:ArkUI 组件树(uitest dumpLayout)、 client RS 节点树(hidumper client,带 frameNodeTag 语义映射)、RS 渲染树(hidumper RSTree)全部可直接 dump; 而 iOS 侧 backboardd 内的 server 树受 task_conversion_eval 平台墙保护,第三方无法观测。 这是鸿蒙(OpenHarmony 工具链)在可观测性上对 iOS 的反向优势。
  3. 可见率鸿沟:鸿蒙浏览器首页 315 个组件 99.7% 在屏内; iOS bilibili 首页 1,162 个 view 树节点仅 15.3% 可见。声明式按需构建(if/LazyForEach) vs 复用池预构建,是两种根本不同的树哲学。
  4. 命令流可直接观测:RS 渲染树上残留的 OtherModifiers 带 pid: 前缀 (SceneBoard 1385 条 / 浏览器 1269 条属性命令),事务队列 transactionFlags[30426, 14696]—— "应用提交命令、RS 进程建树"有了数据级证据,而不只是源码推演。

§1方法论映射:iOS 指标 → 鸿蒙等价通道

之前 iOS 侧的组件树方法论(M1-M6 指标 × 双通道交叉验证)在鸿蒙上找到如下等价物:

指标iOS 通道(iPhone 12 Pro / iOS 14.8)鸿蒙通道(Mate 80 Pro / HarmonyOS 7)
M1 树 dumpdebugserver + lldb recursiveDescriptionhdc shell uitest dumpLayout(ArkUI 可访问性树,JSON)
M1′ 树 dump(第二通道)无(backboardd 树拿不到)hidumper -s RenderService -a "client"(应用侧 RS 节点树,带 frameNodeTag)
M2 存活计数uicount 堆普查(UIView/CALayer 实例数)hidumper RSTree + nodeNotOnTree(树上/不在树上 RS 节点)
M3 类直方图ObjC 类名(MM*×N、LOT*×112 框架指纹)ArkUI 组件类型(Stack/Column/Text…)+ RS 节点类型(CANVAS/SURFACE_NODE…)
M4 内存phys_footprint / task_vm_info/proc/<pid>/status VmRSS + allSurfacesMem(surface buffer 总量)
M6 可见性vis_classify.py 离线分类(递归 window/frame 判定)bounds 矩形 vs 屏幕矩形(1280×2832)直接判定
双时间点稳定t0/t1 树不变t0/t1 dump 节点数一致(315 = 315 ✓)

通道差异说明:iOS 的 recursiveDescription 是进程内对象树全量(含不可访问节点); 鸿蒙 uitest dumpLayout 是可访问性树(ArkUI 对无障碍可见的节点),口径略窄;但 hidumper 的 client/RSTree 通道 补上了 RS 侧全量。三通道互为印证(组件类型分布三通道高度一致),等价性成立。

§2实测设置与协议 EXP

设备HUAWEI Mate 80 Pro(SGT-AL00B),HarmonyOS 7,HongMeng Kernel 1.13.0,aarch64,屏幕 1280×2832
连接hdc(USB),序列号 5SM0125421000216
测量页面系统浏览器(ei.hmos.browser,pid 30426)首页——信息流页面,功能对标 iOS 侧的 bilibili 首页
页面状态拉起后静息 10s+,t0/t1 双时间点 dump 验证稳定(315 = 315 节点)✓
工具链uitest dumpLayout / hidumper -s WindowManagerService / hidumper -s RenderService(RSTree、client、nodeNotOnTree、allSurfacesMem)/ ps -ef / /proc/<pid>/status
系统窗口背景SceneBoard(pid 59356)托管 20+ 系统窗口(桌面/壁纸/状态栏/导航条),浏览器窗口 browser0 为焦点(ZOrd 102)

§3三棵树:同瞬间的全链路观测 EXP

iOS 方法论里"组件树"只有一棵(UIView/CALayer 层级)。鸿蒙真机上同一时刻能观测到三棵语义不同的树:

3.1 第一棵:应用进程内的 ArkUI 组件树(uitest 通道)

App 进程
ArkTS 声明
build() 中的组件描述
App 进程
组件树
315 节点 · 深度 59
App 进程
懒构建
if/LazyForEach 按需实例化 → 99.7% 在屏内
RS 进程
命令提交
类型直方图见下表
指标数值解读
树节点总数(含根)315vs iOS bilibili 首页 1,162 / 微信登录页 63
最大深度59vs iOS 典型 10~20 层——ArkUI 的 Navigation/NavDestination/布局容器层层包装
可见性(M6 等价)99.7% 屏内 + 1 个零尺寸vs iOS bilibili 首页 15.3% 可见——见 §7 架构解读
可点击 / 可滚动30 / 4交互面密度
带资源 id7122.5% 节点有显式 id(自动化/测试锚点)

类型直方图(M3 等价)—— ArkUI 组件指纹

uitest dumpLayout · 浏览器首页类型 Top 12(315 节点)
Stack 54 · Column 52 · Text 44 · Row 39 · __Common__ 15 · SymbolGlyph 13
Flex 11 · Image 10 · GridCol 10 · GridRow 9 · Progress 8 · ListItem 7
(Swiper 4 · CustomFrameNode 4 · Scroll 3 · Navigation 2 · Tabs 2 …)

纯声明式组件指纹:布局容器(Stack/Column/Row/Flex/Grid*)占绝对主体,内容节点(Text/Image/SymbolGlyph)次之。 对应 iOS 侧同类页面直方图里的框架指纹(bili 的 LOT*×112 Lottie 动画层、CAShapeLayer×411)—— 鸿蒙首页没有"隐藏动画层囤积"的痕迹,与其 99.7% 可见率互相印证。

3.2 第二棵:应用进程内的 client RS 节点树(hidumper client 通道)

这是 iOS 侧完全不存在的观测面:RenderService 视角下应用进程自己持有的 RS 节点代理树, 每个节点带 ArkUI 帧节点映射(frameNodeId / frameNodeTag):

hidumper -s RenderService -a "client" · pid 30426树头(节选)
| pid[30426]
transactionFlags:[ 30426, 27762]ImplicitCommonTransactionData, [Command: (Node:131657927491585, Type:3, SubType:26) ...]
  | SurfaceNode[...], name[browser0], isUifirstNode[true], RSCmdModifiers: [type=ABILITY_BG_ALPHA...]
  | RootNode[...], frameNodeId[0], frameNodeTag[root], modifiers[Bounds: 1280x2832...]
    | CanvasNode[...], frameNodeId[1], frameNodeTag[stage]
      | CanvasNode[...], frameNodeId[2], frameNodeTag[page]
        | CanvasNode[...], frameNodeId[7], frameNodeTag[NavBar]
          | CanvasNode[...], frameNodeId[135], frameNodeTag[NavBar] ...
              | CanvasNode[...], frameNodeTag[Stack], RSCmdModifiers: [type=IS_REPAINT_BOUNDARY...]
统计项数值解读
client 树节点总数458(450 CanvasNode + 5 SurfaceNode + 3 RootNode)同一页面:ArkUI 可访问性树 315 < client RS 树 458——RS 侧节点多于可访问性可见节点
frameNodeTag 分布Text 79 · Column 64 · Row 54 · Stack 44 · Image 24 …与 uitest 类型直方图同构(Text 44↔79、Column 52↔64、Stack 54↔44)——三通道互证
事务队列transactionFlags [30426, 27762/27763] ImplicitCommonTransactionData应用在持续提交隐式事务(页面有动画/刷新)
特性标记isUifirstNode[true]、isSingleFrameComposerHarmonyOS 7 的 UIFirst/单帧合成优化路径已启用

frameNodeTag 是黄金证据:ArkUI 组件树的每个帧节点(FrameNode)到 RS CanvasNode 的映射关系在 client dump 里直接可见——"组件树 → 渲染节点树"的转换不是黑盒。

3.3 第三棵:render_service 渲染进程内的 RS 渲染树(hidumper RSTree 通道)

这是前作架构判定的直接验证目标——渲染树到底建在哪个进程:

hidumper -s RenderService -a "RSTree" · RSProcessDump pid:1788树骨架(节选)
| RS_NODE[0]                                          // 渲染树根(进程 pid 1788 内!)
  | SCREEN_NODE[...], screenId[0], Frame[0 0 1280 2832]
    | LOGICAL_DISPLAY_NODE[...], nodeName[SCBScreen-Temp]
      | CANVAS_NODE[...], nodeName[OneHandModeBox]
        | CANVAS_NODE[...], nodeName[transformSceneSCBWallpaper6]   // 每窗口一个场景容器
          | SURFACE_NODE[...], Name[SCBWallpaper6]                 // 壁纸 surface
        | CANVAS_NODE[...], nodeName[transformSceneSCBDesktop8]
          | SURFACE_NODE[...], Name[SCBDesktop8]                  // 桌面 surface
        | CANVAS_NODE[...], nodeName[home], OtherModifiers:[pid:59356->Bounds...pid:30426->...]
          | SURFACE_NODE[...], Name[browser0 / SCBStatusBar17 …]  // 窗口 surfaces
Animating Node: [130678674949881, 130678674949863]               // RS 侧驱动的动画节点
-- RS transactionFlags: [30426, 14696]                          // 浏览器的事务正在队列中
统计项数值解读
渲染树节点总数668全部构建于 render_service 进程(pid 1788)——不在任何应用进程
节点类型CANVAS_NODE 650 · SURFACE_NODE 10 · ROOT 3 · SCREEN/LOGICAL_DISPLAY/EFFECT/UNION 各 1系统级类型学:屏幕→逻辑显示→窗口场景→surface,与 ArkUI 组件类型完全异构
跨进程命令痕迹pid 59356(SceneBoard)1385 条 · pid 30426(浏览器)1269 条 · pid 60258(卡片渲染)86 条OtherModifiers 里带提交者 pid 的属性命令——应用→RS 命令流的数据级证据
动画Animating Node ×2动画节点挂在 RS 树上由渲染进程驱动(对标 iOS 的 server 端插值)
效果节点EFFECT_NODE(FrostedGlassBlur 等)背景模糊等系统效果在 RS 树上以节点形式存在

架构判定验证:668 个渲染树节点、10 个窗口 surface、跨进程命令痕迹、RS 侧动画节点—— 全部观测于 render_service 渲染进程(pid 1788)内部。前作"渲染树由 RS 渲染进程构建并持有"的论断, 从源码推演升级为真机数据实锤。

§4跨进程渲染命令流实证 EXP

RS 渲染树的 dump 不止给出结构,还给出谁在提交命令:

这条证据链与 iOS 侧形成有趣对照:iOS 的 CA 事务走 Mach IPC(内核侧我们锚定了 mach_msg_overwrite_trap),但 backboardd 内部对第三方不可见;鸿蒙的 RS 事务流全程可 dump。

§5内存与"存活 vs 上树" EXP

对象实测值解读
render_service 进程VmRSS 164.7 MB(匿名 91.2MB,46 线程)系统渲染进程的常驻成本——全系统唯一的合成者
浏览器进程VmRSS 741.3 MB(匿名 392MB,124 线程)应用进程只管组件树与内容生产
全系统 surface buffer23.9 MiB(allSurfacesMem)显式缓冲池——对标 iOS 的 IOSurface/BufferQueue 口径
浏览器 WebView surfacesRosenWeb / RosenWebPopup BufferQueue(FIFO=5)网页内容不走 ArkUI 树,以 surface 形式挂载(对标 iOS WKWebView 的 RemoteLayerTree)
不在 RS 树上的节点nodeNotOnTree:WebView 的 surface 节点若干鸿蒙版"存活 vs 上树"指标——RS NodeMap 里注册但未挂到主树的节点(对标 iOS M1/M2 比值语义)

iOS 方法论里最核心的 M1/M2 比值(上树/存活:微信 0.14、bili 0.90)在鸿蒙上有了原生对应物: RenderService 的 NodeMap 本身维护"注册/上树"两态(源码 RegisterRenderNode / UnRegisterUnTreeNode,见 §6),hidumper nodeNotOnTree 直接输出差集。 实测浏览器场景的差集主要是 WebView 双缓冲 surface——语义上与 iOS 的"离屏 layer 存活但不渲染"一致。

§6源码锚定:命令如何变成渲染树 SRC

以下锚点基于 OpenHarmony 开源对应物(HarmonyOS 7 商业版闭源,但 Rosen 图形栈与开源同源; 真机 dump 的符号名与开源实现一一对应):

graphic_graphic_2d: rosen/.../command/rs_canvas_node_command.cppL31-42 · RSCanvasNodeCommandHelper::Create
if (context.GetNodeMap().GetNodeCountByPid(ExtractPid(id)) > MAX_NODE_COUNT_PER_PID) {
	RS_LOGE_LIMIT(... "GetNodeCountByPid > %u, pid:%u", MAX_NODE_COUNT_PER_PID ...);
	return;                                       // ← 每应用渲染节点数有上限(防泄漏)
}
auto node = RSRenderNodeAllocator::Instance().CreateRSCanvasRenderNode(id, ...);
if (context.GetMutableNodeMap().UnRegisterUnTreeNode(id)) { ... }  // ← “不在树上”注册表(nodeNotOnTree 的实现基础)
context.GetMutableNodeMap().RegisterRenderNode(node);         // ← RS 进程内从命令构建节点并注册

这段就是 §3.3 实测的源头:应用发来的 Create 命令在 RS 进程内实例化 RSCanvasRenderNode、 注册进 NodeMap;每 pid 节点数受 MAX_NODE_COUNT_PER_PID 保护; Register/UnRegisterUnTreeNode 双注册表正是 nodeNotOnTree 观测的实现基础。

graphic_graphic_2d: rosen/.../pipeline/main_thread/rs_main_thread.cppL540-591(Init 主循环)+ ProcessCommand
// RS 渲染主线程(真机 pid 1788 的主循环):
ProcessCommand();      // L572 — 消化各应用提交的事务命令(本帧 668 节点树的输入)
Render();             // L591 — “now render is traverse tree to prepare” 遍历树准备渲染

// ProcessCommand() 内部:
if (isUniRender_) ProcessCommandForUniRender();    // 统一渲染 / 分离渲染两条路径
else ProcessCommandForDividedRender();
switch (context_->purgeType_) {                     // ← RS 侧内存回收(对标 iOS purgeable)
	case RSContext::PurgeType::GENTLY:   ClearMemoryCache(...); break;
	case RSContext::PurgeType::STRONGLY: ClearMemoryCache(..., true); break;
}

RS 主线程的循环形状与 iOS render server 的职责对齐:消化命令 → 遍历树 → 动画插值 → 合成; PurgeType::GENTLY/STRONGLY 的两级缓存回收,语义上对标 iOS 侧 purgeable volatile 的 "压力下丢弃可重建内容"。

graphic_graphic_2d: rosen/.../command/rs_node_command.cppL248-269 · DumpNodeTreeProcessor
if (gDumpNodeTreeProcessor) {
	gDumpNodeTreeProcessor(nodeId, pid, token, taskId);   // ← hidumper client/RSTree dump 的实现钩子
}

本文所有树 dump 的合法性也锚在源码上:NodeMap 层预留的 DumpNodeTreeProcessor 回调, 让系统诊断工具能按 pid/token 输出节点树——iOS 把同样的信息锁在平台墙内,OpenHarmony 把它做成了公开工具接口。

§7鸿蒙 vs iOS:组件树维度对比总表

维度鸿蒙(本次真机实测)iOS(前作基线,iPhone 12 Pro / 14.8)
组件树语义ArkUI 声明式:树 = 当前状态推导结果,if/LazyForEach 按需实例化UIKit 命令式:树 = 累积构建结果,复用池/预构建导致存活 ≫ 上树
首页级树规模浏览器首页 315 节点(信息流)bilibili 首页 1,162 节点 / 微信登录页 63
树深度59 层(Navigation/容器层层包装)典型 10~20 层
可见率99.7% 屏内(314/315)bili 首页 15.3%(177/1,162)
渲染树归属render_service 进程(668 节点实测于 pid 1788)——树的本尊在渲染进程应用进程内构建 layer 树,server 侧为提交镜像(backboardd 不可观测)
应用→渲染传输命令/属性流(OtherModifiers pid 痕迹 + transactionFlags 可直接 dump)CA 事务经 Mach IPC(mach_msg_overwrite_trap,内核侧可锚定;server 侧不可见)
动画驱动RS 渲染进程(Animating Node 实测挂 RS 树上)render server 端插值(架构推断,进程内不可直接观测)
"存活 vs 上树"指标NodeMap 双注册表 + nodeNotOnTree 工具(原生支持)M1/M2 双通道测量(lldb 树 + 堆普查,需自建工具)
离屏内容回收RS 侧 PurgeType 两级回收 + nodeNotOnTree 差集purgeable/volatile(内核 token 状态机)+ WebKit volatile 标记
WebView 内容独立 RosenWeb surface(FIFO=5 BufferQueue),不进 ArkUI 树WKWebView RemoteLayerTree(独立 WebContent 进程)
可观测性三棵树 + 命令流 + surface 内存全部可 dump(hidumper/uitest)应用侧树可 dump(lldb);server 侧被平台墙保护(task_conversion_eval)

可见率鸿沟的架构解读:99.7% vs 15.3% 不是"谁更好",而是两种树哲学的必然结果—— ArkUI 的树是状态推导的瞬时快照(不可见分支根本不实例化,LazyForEach 只构建视口内条目), UIKit 的树是历史累积的对象图(复用池、预布局、屏幕外 layer 全部存活)。 这也解释了 iOS 侧 M1/M2 比值能低到 0.14(微信),而鸿蒙侧差集主要是 WebView surface 这种结构性双轨。

§8最终结论

✅ 前作判定被真机实锤

  • RenderService 独立渲染进程存在(pid 1788)且持有全系统唯一渲染树(668 节点);
  • 应用进程只见组件树 + client 代理树,通过命令流向 RS 提交(pid 痕迹 1269 条实测);
  • 渲染树构建权在渲染进程——用户最初的鸿蒙模型描述完全正确;
  • 动画、模糊效果、每窗口 surface 全部以节点形式挂在 RS 树上。

🔍 本次新增的测量发现

  • 鸿蒙可观测性反超 iOS:三棵树 + 命令流 + nodeNotOnTree + surface 内存全链路可 dump;
  • 声明式树哲学:315 节点 99.7% 屏内、深度 59——"树≈当前渲染集";
  • client 树带 frameNodeTag 映射,组件树→渲染树的转换是白盒;
  • RS 侧 PurgeType 两级回收与 iOS purgeable 语义对位。

一句话总结:在鸿蒙真机上,iOS 方法论的每个指标都找到了等价甚至更强的通道—— 更重要的是,前作从源码推演的"应用内组件树 → 命令流 → RS 渲染进程建树渲染"模型, 现在有了进程级(pid 1788)、树级(668 节点)、命令级(OtherModifiers pid 痕迹)三层实测证据。 iOS 把渲染树的观测锁在平台墙内,鸿蒙把它做成了公开诊断接口——这是两平台工程文化的真实差异。

附录证据清单与边界声明

[EXP] uitest dumpLayouthm-browser.json / hm-browser-t1.json浏览器首页 ArkUI 组件树 315 节点(t0=t1 稳定)、类型直方图、99.7% 屏内、深度 59
[EXP] hidumper clienthm-client.txtpid 30426 的 client RS 节点树:458 节点(450 CanvasNode+5 SurfaceNode+3 RootNode)、frameNodeTag 映射、transactionFlags
[EXP] hidumper RSTreehm-rstree.txtrender_service(pid 1788)内 668 节点渲染树:CANVAS_NODE 650 / SURFACE_NODE 10 / 屏幕结构节点;OtherModifiers pid 分布 1385/1269/86/18/3
[EXP] hidumper nodeNotOnTree + allSurfacesMemhm-nottree.txt / hm-surfacemem.txt不在树上的 WebView surface 节点(RosenWeb FIFO=5);全系统 surface buffer 24512 KiB
[EXP] /proc/1788/status · /proc/30426/status真机 procfsrender_service VmRSS 168604 kB(46 线程);浏览器 VmRSS 759116 kB(124 线程)
[SRC] rs_canvas_node_command.cppL31-42RSCanvasNodeCommandHelper::Create:RS 进程内从命令创建节点、MAX_NODE_COUNT_PER_PID 上限、Register/UnRegisterUnTreeNode 双注册表
[SRC] rs_main_thread.cppL540-591 + ProcessCommandRS 主线程循环:ProcessCommand() → Render();UniRender/DividedRender 分支;PurgeType GENTLY/STRONGLY 两级缓存回收
[SRC] rs_node_command.cppL248-269DumpNodeTreeProcessor 钩子:hidumper 树 dump 的开源实现基础

证据边界声明:[EXP] 全部来自 2026-09-01 的 Mate 80 Pro(HarmonyOS 7)真机, 页面为系统浏览器首页静息态;与 iOS 基线(bilibili 首页 / 微信登录页,iPhone 12 Pro / iOS 14.8.1)为 不同 App 的功能对标而非同一 App——量级对比仅供参考,可见率与树哲学的差异为架构级结论。 [SRC] 锚定 OpenHarmony 开源对应物(master 分支),HarmonyOS 7 商业版闭源但真机 dump 的符号名 (RSCanvasNodeCommandHelper / RSMainThread / NodeMap)与开源实现一一对应。 iOS 侧数据引用自前作与 ios-uicount 方法论基线(bili/微信实测,iPhone 12 Pro / iOS 14.8.1)。