双系统组件计数报告 M1 组件树 · M2/M3 类型与对象 · M4 内存 · M5 组件内存 · M6 可见性 · 同一瞬间双通道

📐 渲染管线 × 图片内存变迁全景示意图(SVG)→

节点详情

点击左侧任意节点查看详情

弧长权重:当前「节点数」

环 = 层级深度(内→外)· 弧长 = 子树节点数 · 颜色 = ArkUI 类型分组(■容器布局 ■内容呈现 ■交互输入 ■导航容器 ■其他,越深越暗)· M2 堆普查/M5 组件内存在 ArkUI 无等价通道(对象计数进程外不可达),故本站只有「计数」度量——这是与 iOS 站(四度量切换)的诚实差异 · 悬停看子树规模,点击查节点详情

节点详情

点击旭日图任意环段

读图要点

· 一整圈就是一个 window 的渲染树,从根(中心)到叶子;
· 厚弧大段 = 该分支子树庞大(如设置主页的列表卡片区);
· 细碎密齿 = 大量同类兄弟组件(如 CAShapeLayer 装饰层);
· 颜色随深度变暗,可快速目测「系统壳有多厚、业务层埋多深」;
· 中心数字 = 全树节点总数。
拖拽平移 · 滚轮缩放(以光标为焦点)· 点击节点折叠/展开(火焰图点击缩放)

树上组件 · 按类计数

全部 容器布局 内容呈现 交互输入 导航容器 其他

内存对照

与左图同页面对照

类分布矩形树图

面积 = 计数 · 颜色 = ArkUI 类型分组 · Top 60(悬停看计数)

树深度分布

类型分组构成

树上节点按 ArkUI 类型分组(容器/内容/交互/导航/其他)

hidumper --mem 分类账单

与 iOS 记账口径对照:PSS ≈ phys_footprint 的跨系统近亲(都含共享分摊),但鸿蒙账单天然按「运行时分」分类(ark ts heap / native heap / .so / .hap / GL / Graph·DMA)——这张表本身就是 iOS 侧要靠 malloc zone 分桶 + VM tag 直方图才能拼出来的图景,hidumper 一步给出。swap 列即鸿蒙的 zswap 换出(12GB zswap 池)。

ArkTS 堆 vs native 堆 vs 图形内存

三张账各自的意思:ark ts heap = 声明式组件树/状态管理对象 · native heap = C++ 侧(含 PixelMap 等)· GL/Graph = GPU/DMA 图形缓冲

视图背衬图层(view → backing layer)

每个 UIView 子类实例由哪个 CALayer 子类承载渲染

独立图层节点(sublayer,不在 view 树)

recursiveDescription 下钻 layer.sublayers 的产物(iOS 13+)· 渲染树的一部分

视图 ↔ 图层 汇总

malloc 分区分布(对象在哪个区)

uicount2 按 VM tag 扫描 · tag10=medium 区是 UIView 子类主居所(>255B)
布局 × 热力 · 按真实 frame 定位

悬停查看详情

双系统同 App 对比

同 App 同页面 · iOS 挂起态双通道 / 鸿蒙框架自省 · 条形为等比缩放
鸿蒙侧页面截图
鸿蒙侧页面截图(dump 同时刻 · uitest screenCap)

数值表

对比解读:

iOS vs 鸿蒙 · 图片管线与解码时机对比

iOS 侧为挂起态实测(iPhone 12 Pro · iOS 14.8.1 · 四点曲线实验)· 鸿蒙侧图片管线为文档级推断(渲染树/内存/可见性通道已于 2026-10-12 真机实测,见「方法论与边界」)
核心结论:两套管线在同一条「内存占用 vs 就绪延迟」的权衡曲线上取了不同的点—— iOS 默认惰性(display 阶段才解码 · 解码缓存 purgeable 由系统管压力), 鸿蒙默认急切(createPixelMap 即解码 · LRU 缓存由应用管生命周期)。 各有胜负场景,现有证据不足以得出单边「谁更优」——差距恰好落在 iOS 平台墙盲区与鸿蒙未实测两个区。

iOS · ImageIO 路径 (实测 ✓)

① 加载
压缩 0.86MB
App 堆 ✓footprint
→
display 首绘
② 解码
位图 48MB
App 堆 ✓footprint
+缓存
48MB
purgeable ✗不计
→
commit 上传
③ server
纹理 48MB
✗ App 账 🔒平台墙
惰性红利:隐藏组件只有压缩数据(tag-88 全程 0 实证)· 代价:首次上屏主线程解码 → 大图掉帧。ImageIO 缓存 volatile:内存压力下系统静默回收不杀进程(internal+96MB vs footprint+48MB 的双缓冲巧账)

鸿蒙 · ArkUI 路径 (文档级 ⏳)

① 组件创建
ImageSource
编码态
→
createPixelMap
(eager)
② 解码
PixelMap 48MB
DMA / 堆
→
RS 渲染
③ 零拷贝
dma-buf 导入
全系统一份
+缓存
LRU 256MB
持久 · 默认
急切红利:常显图 DMA 一份全系统共享、首帧就绪不卡 · 代价:隐藏组件默认也吃 48MB 解码内存,直到 LRU 逐出。零拷贝依赖硬解+DMA 路径(软解/部分格式可能仍是堆 PixelMap + 上传——设计上限 ≠ 默认现实,待实测)

触发矩阵 · 何时解码 / 何时绘制

上树只触发「布局」(几何计算,无像素);解码与绘制的触发时机才是两条管线的核心分水岭——iOS 解码由「首次可见」拉动(lazy),鸿蒙解码由「组件创建」推动(eager);两边的「绘制」则同为需求拉动
事件iOS(实测 ✓)鸿蒙(文档级 ⏳)
组件上树 布局 ✓ / 解码 ✗ / 绘制 ✗
86% 层 contents=nil(bili 1,163 层实测:996 层零像素)
布局 ✓ / 解码 ✓ / 绘制 ✗
createPixelMap 即解码,与可见性无关
上树但不可见 零像素成本(惰性红利:仅压缩数据 0.86MB/张,tag-88 全程 0 实证) 解码内存已付,直到 LRU 逐出
首次可见 此时才解码 + 纹理上传
代价:首帧主线程解码 → 大图掉帧
直接用已解码位图
红利:首帧就绪不卡
属性动画 只合成(backboardd 服务端处理,不回 App) 只合成(RS 处理,架构同构)
内容失效重绘 setNeedsDisplay → 下个 runloop turn 的 drawRect/display 标记脏 → 下个 VSync 重绘(Canvas/CustomNode 才有 App 侧光栅化)
懒加载的杠杆位置不同:iOS 懒在解码层——组件可以一直在树上,不显示就不解码(系统策略,应用免费获得); 鸿蒙组件一旦创建就必然解码,所以懒加载杠杆移到组件层(LazyForEach 控制列表项组件要不要创建)——用「不创建组件」间接实现「不解码」,粒度更粗、由应用负责。

渲染时序对比 · 六阶段骨架

两端骨架一致:创建 → 上树 → 布局 → 像素生产 → 合成 → 送显 · 分歧全在像素生产——谁做(App/RS)· 何时(惰性/就绪)· 在哪(CPU/GPU)(点击图片可放大)
iOS vs 鸿蒙 渲染时序对比
维度iOS鸿蒙
像素生产地点App 进程内(CPU:drawRect → CABackingStore 堆,92 层实测)RS 进程(GPU:矢量/文本栅格化在此,App 只录显示列表)
图片解码时点第④步 display 首次需要时(惰性,四点实验实证)第①步组件创建时(eager,解码提前三个阶段)
隐藏视图语义仍参与布局 · 跳过像素生产 · 不合成Hidden 占位不渲染 · None 连布局都退出
重复出现两端一致:不重画——一次生产 · 缓存复用 · 标脏才重画("可见后绘制"的准确说法是"渲染需求驱动")
合成者backboardd(render server)RS / Composer(角色等价物)
App 角色像素生产者(交位图)指令录制者(交显示列表)

工作负载 × 胜者

负载iOS(lazy + purgeable)鸿蒙(eager + DMA + LRU)胜者
常显示、反复用的图堆位图 + server 纹理 = 系统两份DMA 一份全系统共享 · 缓存命中不重解码鸿蒙
大量隐藏/预建图
(bili 首页 63.2% 隐藏类负载)
仅压缩数据 0.86MB/张默认解码 48MB × N,直到 LRU 上限iOS(量级差)
内存压力行为purgeable 静默回收 · 不杀进程LRU 逐出 · 256MB 内持久占用,靠应用调参iOS
首帧延迟首次绘制主线程解码 → 大图掉帧已就绪 → 流畅鸿蒙

对「鸿蒙管线更优」论的前提检验(三处修正)

#原论证检验结果
修正 ①「鸿蒙支持 lazy 可选,所以更优」不成立——两端对称:iOS 可 predecode 转 eager(iOS 15+ preparingForDisplay),鸿蒙可 DIY 转 lazy。差别只是默认值方向,不是能力
修正 ②「iOS 需从 ImageIO 拷贝到 IOSurface」机制说反半步——拷贝是「App 堆 → server 纹理」的一次性上传(server 缓存复用,非每帧);且 iOS 视频路径(CVPixelBuffer)/Metal 本就零拷贝,「必须拷贝」只对静态图成立。实测:解码产物 tag-88 全程 0,从头到尾不是 IOSurface
修正 ③「PixelMap 走 DMA 零拷贝」设计上限 ≠ 默认现实——硬解路径落 DMA,软解/部分格式仍可能堆 PixelMap + 上传;GPU 采样 dma-buf 还受格式/stride 兼容约束(文档级,待实测)

测量盲区与判别实验(A–E 组)

盲区声明:iOS server 纹理在平台墙后(XNU task_conversion_eval ipc_tt.c:2183)——「全系统总量」对比在 iOS 侧只能推算不能实测;鸿蒙侧图片管线细节仍为文档级推断(设备已就位,实验待做)。
组配置验证什么状态
A鸿蒙默认 Image + 隐藏(Visibility.None)eager 基线:隐藏态是否就吃 48MB 解码内存(hidumper --mem 查 PSS/dma_heap)⏳ 设备已就位,待做
B鸿蒙 LazyForEach 屏外 item组件级懒的边界:组件不创建是否即不解码⏳ 设备已就位,待做
C鸿蒙 DIY:持 ImageSource + 可见才 createPixelMap应用级懒 = iOS 语义复刻(四点法直接套用)⏳ 设备已就位,待做
DiOS 默认(隐藏图片组件)lazy 基线:隐藏态只有压缩数据✓ 已测(tag-88=0 · 压缩 0.86MB)
EiOS predecode(offscreen 强制绘制)eager 形态复刻:验证「两端默认值不同、能力对等」⏳ 可立即做
判定标准:若 A 组证实隐藏即解码、E 组证实 iOS 可复刻 eager → 结论「默认值方向不同、各有胜负场景」成立;若鸿蒙默认在全负载不差且 C 组最优配置 ≤ iOS → 才能写「管线设计占优」;只要隐藏预建负载鸿蒙显著更差,「鸿蒙更优」就必须反写。
延伸:📐 渲染管线 × 图片内存变迁全景图 · 🕐 渲染时序对比图(六阶段骨架) · iOS 侧四点曲线实验设计 raw-data/exp-decode-2026-09-24.md · 解码触发时点 = CA 周期 layout → display(解码在此) → commit(运输)

方法论映射 · iOS 通道 → 鸿蒙通道

指标iOS 通道(已验证)鸿蒙通道(本次实测)状态
M1 组件树debugserver 重签直连 + lldb recursiveDescription(挂起态)hdc shell uitest dumpLayout(框架自省)✓ 双通
M3 类型直方图uicount 堆普查按类计数dump 的 type 属性直接聚合✓ 双通(口径不同:类 vs 组件类型)
M4 内存mach task_vm_info(phys_footprint/internal)+ TAGHISThidumper --mem(PSS 分项 + swap + heap)✓ 双通(口径:footprint vs PSS)
M6 可见性静态分类器(hidden 传播/裁剪/离屏/遮挡启发式)框架 visible 属性(真值)+ bounds 离屏判定✓ 双通(鸿蒙侧是框架真值,更强)
M2 存活对象计数uicount 堆扫描(255,737 ObjC 对象实名)—✗ 无等价通道(ArkUI 对象计数进程外不可达)
M5 组件内存归因class_ro_t.instance_size 全类表 join—✗ 依赖类尺寸表,同因不可达

已验证的采集命令(鸿蒙侧)

HDC=~/.dsh/skills/ohos-device-lab/bin/hdc
"$HDC" shell 'aa start -a EntryAbility -b com.tencent.wechat'             # 启动(需显式 ability 名)
"$HDC" shell 'uitest dumpLayout -p /data/local/tmp/ohos_uicount/x.json'   # 组件树 dump(≈M1)
"$HDC" shell 'uitest screenCap -p /data/local/tmp/ohos_uicount/x.jpg'     # 同时刻截图
"$HDC" shell 'hidumper --mem <pid>'                                   # 内存账单(≈M4)
"$HDC" shell 'hidumper -s WindowManagerService -a "-a"'                   # 窗口/z序(pid/焦点)
坑:aa start 隐式启动失败(Error 10103101)——必须 bm dump -n 查 mainElementName 显式指定;设置入口是 com.huawei.hmos.settings.MainAbility。
诚实的边界:① uitest dump 是框架自省——只能看到 ArkUI 想让你看的(自定义绘制内部、RS 侧光栅化细节不可见),与 iOS debugserver 的「原始内存外挂式」通道不同级; ② 鸿蒙无 M2/M5 等价物,库存结构(预构建/隐藏囤积)只能从树侧观测; ③ 本次两页面均为静止态,未做滚动/列表/动画下的动态对比; ④ PSS 与 phys_footprint 是不同口径的近亲,跨系统数值比较只看量级与结构。