RNOH 黑屏白屏与渲染异常定位指南
RNOH 应用启动白屏/黑屏、组件渲染异常、运行期冻屏的排查方法论与定位指南
在 RNOH 开发中,"应用一片白/黑""组件不显示或显示错""滑动一会就卡死"是最常见、也最易误判的问题类型之一。本文档给出从现象到根因、从分类到处置的系统化定位方法。
一、问题概述
1.1 什么是"黑屏、白屏、渲染异常"
RNOH 的渲染链路较长:JS → TurboModule 桥接 → C++ 适配层(SchedulerDelegate / MountingManagerCAPI)→ ArkUI 节点层(arkui/ArkUINode、NativeNodeApi)→ @ohos C-API。任一环节断裂或错配,都会在屏幕上表现为"看不到内容"或"内容不对"。常见表现:
- 启动白/黑屏:应用启动后始终停留在加载页/空白页,RN UI 永不出现;
- 渲染异常:UI 出现但组件不显示、属性不生效、事件不回调、布局抖动或文本不可见;
- 运行期冻屏/黑屏:UI 一度正常,操作后画面冻结、点击无响应或转黑。
1.2 为什么必须先分类再处理
这三类现象的根因分布在链路完全不同的环节,处置方向相反:
- 把启动白屏(bundle 没加载/so 没打包)当成渲染 Bug 去查组件树,会南辕北辙;
- 把渲染异常(props 未下发/事件名映射)当成性能问题去优化,会越改越乱;
- 把冻屏(C++ 死锁)当成 JS 逻辑 Bug 去改业务代码,会错过框架修复。
因此定位流程的第一步是按现象分类、按链路分层,而不是一开始就翻组件源码。
1.3 三类问题的性质
| 类别 | 定义 | 链路环节 | 是否需修复 |
|---|---|---|---|
| 启动白/黑屏 | bundle 未加载、so 未打包、窗口未就绪导致 UI 永不出现 | JSBundleProvider / so 加载 / RNAbility 窗口 | ✅ 配置或升级 |
| 渲染异常 | UI 出现但 props/事件/布局/文本有误 | MountingManagerCAPI / ComponentInstance / ArkUI 节点 | ✅ 适配层或业务 |
| 运行期冻屏/黑屏 | UI 一度正常后因死锁/重负载冻结 | C++ 锁/线程、主线程 I/O | ✅ 框架修复或异步化 |
二、排查思路与整体分析
2.1 核心判定流程
定位的关键是回答一个问题:**"看不到"发生在哪个环节——bundle 没进来、组件树没建/建错、还是建好后被锁住?**
flowchart TB
A["黑屏 / 白屏 / 渲染异常"] --> B{"UI 是否曾出现?"}
B -->|"从未出现"| C{"bundle 是否加载成功?"}
C -->|"否: Metro 超时/文件不存在"| D["分类: 启动白屏-bundle 链<br/>→ 查 JSBundleProvider"]
C -->|"是: 但崩溃/so 缺失"| E{"FaultLog 是否报 so/lib?"}
E -->|"是: libRNOHApp/libhermes/RNOH_C_API_ARCH"| F["分类: 启动白屏-so/配置<br/>→ 修构建配置"]
E -->|"否: 窗口未加载 content"| G["分类: 启动白屏-窗口<br/>→ 查 RNAbility.onAppSetup"]
B -->|"出现过但错/不显示"| H{"是单组件错还是整体?"}
H -->|"单组件 props/事件/文本"| I["分类: 渲染异常<br/>→ ArkUI Inspector + onPropsChanged"]
H -->|"布局抖动/死循环"| J["分类: 渲染异常-布局<br/>→ 查 onLayout/Yoga 舍入"]
B -->|"出现后冻结/转黑"| K{"AppFreeze? 主线程阻塞?"}
K -->|"是: std::mutex::lock/死锁"| L["分类: 运行期冻屏<br/>→ 查 C++ 锁/线程"]
K -->|"是: 主线程繁忙/重负载"| M["分类: 运行期冻屏-繁忙<br/>→ 拆分初始化任务"]
2.2 排查总思路:沿渲染链路逐层定位
渲染异常定位的本质是沿链路找到断裂/分叉点。RNOH 的渲染链路为:
JSBundleProvider 加载 bundle → JS 执行 → SchedulerDelegate(willMount/doMount/didMount)
→ MountingManagerCAPI(handleMutation/doMount) → ComponentInstance(onPropsChanged)
→ arkui/ArkUINode + NativeNodeApi(@ohos C-API)
排查应按这条链路自上而下逐层确认,每层都读真实源码:
- bundle 是否进来 —
JSBundleProvider链是否拿到 bundle(见 [源码]src/main/ets/RNOH/JSBundleProvider.ts); - 组件树是否建/建对 —
MountingManagerCAPI::doMount/handleMutation是否执行、mutation 是否正确(见 [源码]src/main/cpp/RNOH/MountingManagerCAPI.cpp); - props 是否下发 —
ComponentInstance::onPropsChanged是否收到正确值(见 [源码]RNOHCorePackage/ComponentInstances/*ComponentInstance.cpp); - ArkUI 节点是否生效 —
arkui/ArkUINode调用@ohosC-API 是否成功; - 是否被锁住 — 冻屏则查 C++ 锁/线程(
FontRegistry、TurboModuleProvider、SchedulerDelegate)。
关键原则:先用 ArkUI Inspector / hilog 定界"在哪一层断裂",再深入该层读源码,不要一开始就逐文件翻组件实现。
2.3 黑屏白屏渲染异常常见模式速查
| 模式 | 分类 | 说明 |
|---|---|---|
| Metro 超时白屏 | 启动白屏 | 无 Metro 服务时 MetroJSBundleProvider 仍优先尝试 WebSocket 连接 |
| bundle 缓存旧版本白屏 | 启动白屏 | FileJSBundleProvider 设备缓存优先级高于 rawfile,HAP 更新后加载旧 bundle |
| so 缺失崩溃黑屏 | 启动黑屏 | libRNOHApp/libhermes.so 未打进 HAP,libRNOHApp is undefined |
| CAPI 未配置闪退 | 启动黑屏 | 未设 RNOH_C_API_ARCH 启动即崩 |
| 窗口 801 卡加载页 | 启动白屏 | windowRectChange 设备不支持(801),loadContent 被跳过 |
| props 未下发/枚举漏项 | 渲染异常 | onPropsChanged 收不到值或枚举转换 abort 兜底 |
| 事件名映射错 | 渲染异常 | emitComponentEvent 用 onStop 而非 stop |
| Yoga 舍入 onLayout 死循环 | 渲染异常 | 3x 屏浮点舍入震荡,文本反复触发 layout |
| Text 不渲染 | 渲染异常 | CAPI 文字管线与设备兼容性问题 |
| 死锁冻屏 | 运行期冻屏 | FontRegistry/TurboModuleProvider/Surface 锁顺序不一致 |
| worker 同步取窗口卡死 | 运行期冻屏 | SafeAreaInsetsProvider 在 worker 同步调窗口属性 |
三、分步排查过程(工具使用)
源码查阅:RNOH 源码在本地仓库
packages/tester/harmony/react_native_openharmony/{文件路径};RNOH 文档在线:https://gitcode.com/CPF-RN/ohos_react_native/blob/main/docs/zh-cn/{文件路径}。
3.1 排查准备:定界现象与采集现场
第一步不是读源码,而是把现象钉死到某一类、采集现场:
- 判断 UI 是否曾出现:完全空白 → 启动期;出现后错 → 渲染异常;出现后冻结 → 运行期冻屏。
- 采集日志:
- 启动白屏:
hilog抓启动链日志,重点看JSBundleProvider、RNAbility.onAppSetup、so 加载; - 崩溃黑屏:DevEco Studio
FaultLog取CppCrash/JS Crash; - 冻屏:
FaultLog取AppFreeze(THREAD_BLOCK_6S/APP_INPUT_BLOCK/LIFECYCLE_TIMEOUT),并hdc file recv /data/log/faultlog/faultlogger/ .(见 [在线源码]docs/zh-cn/03-调测/RNOH应用稳定性问题定位指南.md§3)。
- 启动白屏:
- 抓 trace:仅靠卡死栈无法判断主线程"忙"还是"堵",用 DevEco Studio Frame/trace 补充现场(见稳定性定位指南 §3.2)。
- 环境自检:确认
RNOH_C_API_ARCH、构建模式(Debug/Release)、Metro 是否在跑、bundle 来源(设备文件 vs rawfile vs 服务器)。
3.2 步骤 1:定位启动白屏——bundle 与 so 链路
目标:确认 bundle 是否加载成功、so 是否齐备、窗口是否加载 content。
bundle 链路(见 [源码] JSBundleProvider.ts):
AnyJSBundleProvider按数组顺序逐个尝试 provider,先成功者胜(getBundle第 375 行循环);MetroJSBundleProvider优先级最高时,无 Metro 服务会先尝试 WebSocket 连接,超时白屏;FileJSBundleProvider从设备路径读取(fs.access(this.path),第 115 行),优先级高于 rawfile,HAP 更新后不清除——是"装了新包还白屏/行为没变"的高频根因;ResourceJSBundleProvider从 rawfile 加载(getRawFdSync,第 168 行),是 release 包的正常来源。
常见根因与处置(见 [在线源码] docs/zh-cn/03-调测/运行类FAQ.md、定位定界指导类FAQ.md):
| 现象/日志 | 根因 | 处置 |
|---|---|---|
启动闪退,提示未设 RNOH_C_API_ARCH | CAPI 版本未声明 | 设 RNOH_C_API_ARCH=1,重启 IDE,Clean 后重编;不生效删 .cxx |
Coundn't create bindings between ETS and CPP. libRNOHApp is undefined | librnoh_app.so 缺失或 libhermes.so 未打包 | 查 entry/build/.../arm64-v8a/ 是否有 so;补 build-profile.json5 的 externalNativeOptions;确认 libhermes.so 打进 HAP |
混合方案闪退 insertChild | CustomRNComponentFrameNodeFactory 未正确创建 | 正确创建工厂或升级 RNOH |
混合方案 Cannot read property tag of undefined | 创建 RNApp/RNInstance 未传 arkTsComponentNames | 把组件 name 放数组作 arkTsComponentNames 传入 |
cannot find record '&@rnoh/...generated/ts&<版本>' | codegen ets 生成到 oh_modules,运行时找不到 | 自 CLI 0.77.23 起用 --ets-output-path 生成到源码目录并调整 import |
Metro Websocket connection failed | 无 Metro 服务仍优先连服务器 | 注释 MetroJSBundleProvider,强制用本地 bundle |
| 装新 HAP 行为不变 | FileJSBundleProvider 设备缓存未清 | hdc shell "rm -f /data/storage/el2/base/files/bundle.harmony.js" |
| 断开设备重启后白屏 | 用了 Metro 热加载 | 终止 Metro,按 release bundle 重打包(见 定位定界指导类FAQ.md) |
3.3 步骤 2:定位启动白屏——窗口与 content 加载
目标:确认 RNAbility 窗口监听注册与 loadContent 是否被调用。
链路(见 [源码] src/main/ets/RNOH/RNAbility.ets):
onWindowStageCreate→onAppSetup(第 208 行):依次执行unsubscribeWindowListeners→onPrepareRNInstance→getMainWindow→subscribeWindowListeners(第 222 行)→onWindowSetup→onWindowStageSetup(loadContent,第 225 行);subscribeWindowListeners(第 140 行)注册windowSizeChange与windowRectChange。
关键陷阱:windowRectChange 在部分设备(如 OpenHarmony 开发板)不支持,抛错误码 801(Capability not supported)。若 subscribeWindowListeners 抛 801 且无兜底,loadContent 被跳过 → 应用永远卡在加载页(白屏)。
处置:
- 0.72.141(ArkTS)需在
RNAbility.ets为subscribeWindowListeners各监听单独 try-catch,并在外层 catch 兜底调用onWindowStageSetup(见docs/开发者指南/03-开发调试/OpenHarmony运行RN应用.md§2.5); - 0.82.30(CAPI)已在基类内置等效保护,一般无需手动 patch;
- 开发板固定分辨率,
windowRectChange失败不影响核心功能,可安全兜底。
非致命错误可忽略:
Failed to get customDensity、Failed to get maxFontScale {code:1300002}、Turbo Module 'RedBox' not found、RSRenderNodeDrawableAdapter node type 129 not supported等均不影响渲染(见OpenHarmony运行RN应用.md§2.8)。
3.4 步骤 3:定位渲染异常——组件树与 props
目标:UI 出现但错,定位是组件没建、props 没下发,还是 ArkUI 节点没生效。
核心工具:ArkUI Inspector(见 [在线源码] 定位定界指导类FAQ.md §常见属性效果排查):
- DevEco Studio 底部
ArkUI Inspector,选目标应用,按文本/类型/tag 找组件; - 选中组件看右侧属性,定位异常属性;
- 属性值有两个来源——
handleMutation(组件树操作)与Animated NativeDriver(动画驱动)。
源码打点定位(见 [在线源码] 定位定界指导类FAQ.md):
| 打点位置 | 文件 [源码] | 判断什么 |
|---|---|---|
| 组件创建/移除顺序 | src/main/cpp/RNOH/SchedulerDelegateCAPI.h handleMutation | mutation 是否按预期执行、创建顺序是否错 |
| 动画驱动属性更新 | src/main/cpp/RNOH/SchedulerDelegateCAPI.cpp synchronouslyUpdateViewOnUIThread | 哪一步属性更新出错 |
| props 下发 | RNOHCorePackage/ComponentInstances/*ComponentInstance.cpp onPropsChanged | 组件是否收到正确 props(用 getTag 关联) |
渲染管线源码事实([源码]):
SchedulerDelegate实现willMount/doMount/didMount/handleMutation/updateView/reportMount(见src/main/cpp/RNOH/SchedulerDelegate.h);MountingManagerCAPI::doMount带TraceSection "#RNOH::MountingManager::doMount"(MountingManagerCAPI.cpp第 62 行),handleMutation处理 Create/Update/Remove(第 208 行,removeChild在第 278 行);- ArkUI 节点层在
src/main/cpp/RNOH/arkui/(ArkUINode.cpp、NativeNodeApi.cpp、NodeApi.cpp、TextInputNode.cpp、SpanNode.cpp等),调用@ohosC-API。
常见根因:
| 现象 | 根因 | 处置 |
|---|---|---|
| props 不生效 | 枚举转换对合法值 abort()/返回 -1 兜底被静默处理 | 查 conversions.h 转换函数,补全合法值映射 |
| Fabric 组件事件不回调 | emitComponentEvent 用 onStop,而 GeneratedEventEmitRequestHandler 的 supportedEventNames 是 stop(JSI 层自动加 on) | 改用 stop 作为事件名(见 定位定界指导类FAQ.md) |
| 图片加载不出 | 本地图片路径应为 rawfile/assets,沙箱图片不需 assets 层;或 copyAssets 未执行 | 在 ImageComponentInstance.cpp onPropsChanged 打印路径与 assets 比对 |
| 文本不可见(0.82.30 CAPI) | CAPI 文字管线与设备兼容性问题,非应用层可修 | 用 0.72.141(ArkTS 路径)或 <TextInput editable={false}> 替代 <Text>(见 OpenHarmony运行RN应用.md §2.7) |
| 整体性能极差/白屏 | 用了 Metro 热加载/dev 打包/Debug 构建 | 改 release 构建模式(见 定位定界指导类FAQ.md §整体性能) |
3.5 步骤 4:定位渲染异常——布局抖动与文本
目标:布局反复触发、文本测量偏差等问题。
典型根因(见 docs/zh-cn/RNOH与RN社区共性问题清单.md):
- onLayout 无限循环:Yoga
YGRoundValueToPixelGrid浮点舍入震荡,3x 屏(pointScaleFactor=3)高发;父节点onLayout监听文本宽度并刷新left坐标,每次舍入结果不同 → 死循环。处置:用right替换left,或left/width取整(PixelRatio.roundToNearestPixel())。 - Text 字体缩放间距异常:
allowFontScaling极值档位下测量与渲染字号换算不一致。处置:关allowFontScaling或用maxFontSizeMultiplier限制。 - TextInput
transparent文字仍可见:上游UndefinedColor从INT32_MAX改为 0,SharedColor::operator bool()对 transparent 返回 false 被当未设置。处置:用color: '#ffffff00'代替transparent。
上述均为 RN 上游共性问题(iOS/Android/鸿蒙一致),非 RNOH 独有 Bug,按社区方案规避即可。
3.6 步骤 5:定位运行期冻屏——主线程阻塞
目标:UI 一度正常后冻结,判断主线程"忙"还是"堵"。
判定方法(见 [在线源码] RNOH应用稳定性问题定位指南.md §3):
| 特征 | 主线程繁忙 | 主线程阻塞 |
|---|---|---|
| 3s 与 6s 堆栈 | 不完全一致 | 高度一致,停在固定位置 |
| 线程状态 | Running | Sleeping/Waiting |
| 常见调用 | 渲染/布局/组件创建/JS 执行 | std::mutex::lock/I/O/同步等待 |
死锁判定要点:
- 堆栈出现
std::mutex::lock不一定是死锁,也可能是锁被后台耗时任务长期持有; - 对比 3s/6s 堆栈是否停在同一位置,查其他线程是否持有同一把锁或等待主线程;
- 画线程间锁持有/释放时序,确认是否存在互相等待。
历史冻屏根因速查(见 [在线源码] docs/zh-cn/05-运维/稳定性历史修复/):
| 冻屏 | 版本 | 根因 | 修复 |
|---|---|---|---|
| TimingTurboModule 与 NativeAnimated 死锁 | 0.72.27 | 同步调用链相互等待 | 拆解模块间同步调用 |
| Surface 死锁 | 0.72.27 | 多 Surface 启停时 RNInstance/NapiBridge/SurfaceHandle 同步顺序不一致 | 统一 surface 生命周期同步顺序 |
| getTurboModule 持锁创建 TM 死锁 | 0.77.18 | 持 m_cacheMtx 时同步执行 m_createTurboModule 致锁重入 | 拆成"先读缓存命中即返,未命中释放锁再创建" |
| 主线程销毁 RNInstance 死锁 | 0.77.18/0.72.86 | 主线程持最后引用触发销毁,TurboModuleProvider 重新锁实例 | 引入 SafeWeak/m_isAboutToBeDestroyed 标记 |
| FontRegistry 锁顺序死锁 | 0.82.3/0.77.44 | JS 线程与 UI 线程对两把锁加锁顺序相反 | 改 std::scoped_lock 原子加锁(见 [源码] FontRegistry.cpp 第 77/94 行) |
| reportMount 锁重入 | 0.82.17/0.77.59 | reportMount 在持锁上下文重入 ShadowTreeRegistry 锁 | 切主线程任务队列异步执行 |
| SafeAreaInsetsProvider worker 卡死 | 0.72.112 | worker 线程同步调 getWindowProperties 切 UI 线程同步等待 | 从 worker TM context 移除该 provider |
符号化:C++ 堆栈需用 llvm-addr2line -e <unstripped.so> <addr> 反解;必须确认 BuildID 一致、用 unstripped.so,否则反解到错误行(见稳定性定位指南 §5.4)。
3.7 工具速查
| 工具 | 用途 | 示例 |
|---|---|---|
| ArkUI Inspector | 查组件树与属性 | 定位 props 不生效的组件 |
| DevEco FaultLog / hilog | 崩溃/冻屏/启动日志 | 取 CppCrash/AppFreeze,看 so 加载、bundle 链 |
| DevEco Frame/trace | 主线程耗时区间 | 区分冻屏"忙"还是"堵" |
llvm-addr2line | C++ 堆栈符号化 | 反解 librnoh_core.so 地址到 FontRegistry::addFontData |
hdc shell rm | 清 bundle/缓存 | 清 FileJSBundleProvider 设备缓存 |
| React DevTools | JS prop 下发与组件树 | 确认 prop 实际传入值 |
| AI 辅助定位 | 堆栈 + 版本 + 设备 → 根因 | rnoh-render-analyzer(触发词:白屏、黑屏、渲染异常、冻屏) |
四、根因定位
4.1 定位方法:找到链路断裂点
根因定位的本质是沿渲染链路逐层确认,直到找到断裂/分叉点:
- 从 2.1 流程定界到某一类(启动/渲染/冻屏);
- 在该类对应的链路层打点/读源码,定位断裂点;
- 用 4.2 模式表识别类型,用 4.3 五问模板固化结论。
示例点拨:启动白屏,
hilog显示can't find library libhermes.so,且FileJSBundleProvider缓存被优先加载 → 断裂点在 so 打包与 bundle 缓存两处。根因是libhermes.so未打进 HAP + 设备残留旧 bundle。处置:确认nativeLib.excludeFromHar配置 +hdc shell rm清缓存。
4.2 分叉模式识别指南
对照 2.3 速查表,每种给出"如何识别"与"在哪找":
| 模式 | 如何识别 | 在哪找 |
|---|---|---|
| Metro 超时白屏 | 日志有 Websocket connection failed | JSBundleProvider.ts MetroJSBundleProvider |
| bundle 缓存旧版 | 装新 HAP 行为不变,日志显示加载设备文件 | FileJSBundleProvider.getBundle + hdc shell 查缓存文件 |
| so 缺失黑屏 | libRNOHApp is undefined / can't find library libhermes.so | entry/build/.../arm64-v8a/ 与 HAP libs/ |
| 窗口 801 卡加载 | Failed to setup window {code:801} + 卡加载页 | RNAbility.ets subscribeWindowListeners |
| props 未下发 | Inspector 组件存在但属性缺/错 | *ComponentInstance.cpp onPropsChanged + conversions.h |
| 事件名映射错 | emitComponentEvent 不回调 | RNOHGeneratedPackage.h supportedEventNames |
| 布局死循环 | onLayout 高频触发、文本反复刷新 | Yoga YGRoundValueToPixelGrid(3x 屏高发) |
| 死锁冻屏 | AppFreeze + 3s/6s 堆栈停 mutex::lock 同一位置 | FontRegistry/TurboModuleProvider/SchedulerDelegate |
4.3 结论五问模板
每次定位完,用下表固化结论(作为待填模板,而非预填答案):
| 问题 | 含义 |
|---|---|
| What | 现象是什么(白屏/黑屏/组件错/冻屏,JS 侧可观察) |
| Where | 链路何处断裂(指明文件 + 链路层,源码或在线源码) |
| Why | 为什么断裂(bundle/so/props/锁/布局) |
| 分类 | 启动白/黑屏 / 渲染异常 / 运行期冻屏 |
| 分类依据 | 为什么归入此类(引用日志/堆栈/Inspector 证据) |
五、解决方案
5.1 何时提出修复
仅在以下情况提出修复:
- 命中已修复的历史冻屏/崩溃且当前版本低于修复版本 → 升级(见《历史版本问题定位指南》);
- 是适配层缺口(props 枚举漏项、事件名映射、so 未打包) → 补适配或修配置;
- 是业务侧布局问题(onLayout 死循环、字体缩放) → 按社区方案规避。
以下情况不要提出修复:现象是上游共性问题(按社区方案规避即可);现象由 Metro 热加载/dev 构建/Debug 模式导致(改 release 构建即可);开发板设备能力限制(如 801,兜底处理即可)。
5.2 修复策略
适用时按以下策略之一落地。任何方案都必须指明文件、改动思路与风险评估,并配套测试。
| 策略 | 适用场景 | 方法 |
|---|---|---|
| 修正 bundle/so 配置 | 启动白/黑屏 | 注释无用的 MetroJSBundleProvider;补 externalNativeOptions 与 nativeLib.excludeFromHar;确认 libhermes.so 打进 HAP;清 FileJSBundleProvider 缓存 |
| 窗口监听兜底 | 801 卡加载页 | RNAbility.ets 为 subscribeWindowListeners 各监听单独 try-catch,外层 catch 兜底 onWindowStageSetup |
| 补全 props/事件映射 | 渲染异常 | conversions.h 补全枚举映射;emitComponentEvent 用去 on 前缀的事件名;ImageComponentInstance 对齐路径 |
| 修正布局 | 布局死循环/文本异常 | left→right 或取整;关 allowFontScaling;transparent→#ffffff00 |
| 异步化/拆任务 | 主线程繁忙冻屏 | 复杂计算移出主线程;拆页面初始化;同步 I/O 改异步 |
| 升级修复死锁 | 死锁冻屏 | 升级到含 scoped_lock/SafeWeak/异步 reportMount 修复的版本 |
风险评估要点:窗口兜底改动需回归正常 Phone/Tablet 设备的窗口行为;props 映射补全需对照 ArkUI 枚举选语义最近项;异步化需确认回调时序不破坏 RN 契约。
5.3 仅文档输出(现象为设备限制/上游共性时)
当分类为"设备能力限制"(如开发板 801)或"上游共性问题"时,输出应:清晰记录现象及成因;解释设备/平台机制;说明开发者侧影响;给出规避模式(如"用 right 代替 left""用 #ffffff00 代替 transparent""开发板固定分辨率可忽略 801")。
六、经验总结
6.1 六条核心教训
- 先分"从未出现"还是"出现后错" — 完全空白走启动链路(bundle/so/窗口),出现后错走渲染链路(props/事件/布局),混不得。
- 白屏先查 bundle 缓存与 so 打包 —
FileJSBundleProvider设备缓存优先级最高且 HAP 更新不清;libhermes.so未打包是libRNOHApp is undefined的常见根因。 - 渲染异常先用 ArkUI Inspector 定界 — 用 Inspector 看组件是否存在、属性是否对,再决定去
onPropsChanged还是conversions.h打点,不要盲目翻源码。 - 事件名要去
on前缀 —emitComponentEvent用stop不是onStop,supportedEventNames存的是去前缀名,JSI 层自动加on。 - 冻屏先判"忙"还是"堵" — 对比 3s/6s 堆栈:一致是阻塞(查锁),不一致是繁忙(拆任务);
mutex::lock不一定是死锁。 - 不要跳过符号化与 BuildID 核验 — C++ 堆栈必须用 BuildID 匹配的 unstripped.so 反解,否则反解到错误行;每条引用指向真实读过的源码,未读标
[待确认]。
6.2 排查清单(Checklist)
- 已判断 UI 是"从未出现"还是"出现后错/冻结"
- 启动白屏:已查 bundle 链(Metro/文件/rawfile)与 so 打包
- 已清
FileJSBundleProvider设备缓存后复现 - 渲染异常:已用 ArkUI Inspector 定位组件与属性
- 已在
onPropsChanged/handleMutation/synchronouslyUpdateViewOnUIThread打点 - 冻屏:已取 AppFreeze 并对比 3s/6s 堆栈判"忙/堵"
- C++ 堆栈已用 BuildID 匹配的 unstripped.so 符号化
- 已确认非 Metro 热加载/dev 打包/Debug 构建导致的性能假象
6.3 常见误区对照
| 误区 | 正解 |
|---|---|
| "白屏就是 RN 渲染 Bug" | 先查 bundle 是否加载、so 是否齐备、窗口是否 loadContent |
| "装了新 HAP 还是白屏,代码没改对" | FileJSBundleProvider 缓存优先级高于 rawfile,先 hdc shell rm |
| "事件不回调是组件没注册" | 可能是事件名带了 on 前缀,supportedEventNames 存的是 stop |
| "冻屏 + mutex::lock 一定是死锁" | 先对比 3s/6s 堆栈,可能只是锁被后台耗时任务持有 |
| "addr2line 反解出来了就是那儿" | 必须先确认 BuildID 一致 + 用 unstripped.so,否则反解错误 |
| "开发板白屏是 RNOH Bug" | 可能是 windowRectChange 801 设备能力不支持,兜底即可 |
附录
附录 A:渲染链路与断裂高发区
下图标注了黑屏白屏渲染异常的断裂高发层:
flowchart TB
subgraph LOAD["启动加载层(白屏高发区)"]
L1["JSBundleProvider<br/>⚠️ Metro 超时/File 缓存"]:::warn
L2["so 加载<br/>⚠️ libRNOHApp/libhermes 缺失"]:::warn
L3["RNAbility 窗口<br/>⚠️ windowRectChange 801"]:::warn
end
subgraph TREE["组件树层(渲染异常高发区)"]
T1["SchedulerDelegate<br/>willMount/doMount/handleMutation"]:::ok
T2["MountingManagerCAPI<br/>⚠️ props 枚举漏项/abort"]:::warn
T3["ComponentInstance<br/>⚠️ onPropsChanged 未收到值"]:::warn
end
subgraph NODE["ArkUI 节点层"]
N1["arkui/ArkUINode + NativeNodeApi<br/>@ohos C-API"]:::ok
N2["⚠️ Text CAPI 管线兼容性"]:::warn
end
subgraph FREEZE["运行期冻屏层(黑屏高发区)"]
F1["C++ 锁/线程<br/>⚠️ FontRegistry/TurboModule/Surface 死锁"]:::warn
F2["主线程繁忙<br/>⚠️ 重负载/同步 I/O"]:::warn
end
L1 --> L2 --> L3 --> T1 --> T2 --> T3 --> N1 --> N2
T1 -.冻屏.-> F1
T1 -.冻屏.-> F2
classDef warn fill:#fef3c7,stroke:#d97706
classDef ok fill:#dcfce7,stroke:#16a34a
断裂高发区说明:
| 层 | 高发模式 | 排查动作 |
|---|---|---|
| bundle 链 | Metro 超时/设备缓存旧版 | 查 JSBundleProvider.ts + 清 FileJSBundleProvider 缓存 |
| so 加载 | libRNOHApp/libhermes.so 缺失 | 查 HAP libs/arm64-v8a + nativeLib 配置 |
| 窗口 | 801 卡加载页 | RNAbility.ets subscribeWindowListeners 兜底 |
| props 解析 | 枚举漏项/abort 兜底 | conversions.h + onPropsChanged 打点 |
| 事件映射 | 事件名带 on 前缀 | RNOHGeneratedPackage.h supportedEventNames |
| C++ 锁 | 死锁/锁重入 | 3s/6s 堆栈对比 + 升级含 scoped_lock 修复版本 |
附录 B:判定流程图(完整版)
flowchart TB
A["黑屏/白屏/渲染异常"] --> B{"UI 是否曾出现?"}
B -->|"从未出现"| C{"bundle 加载成功?"}
C -->|"否"| D["启动白屏-bundle 链<br/>→ Metro/缓存/FileJSBundleProvider"]
C -->|"是"| E{"FaultLog 报 so/lib?"}
E -->|"是"| F["启动黑屏-so/配置<br/>→ RNOH_C_API_ARCH/libhermes/nativeLib"]
E -->|"否"| G["启动白屏-窗口<br/>→ RNAbility 801 兜底"]
B -->|"出现后错"| H{"单组件还是整体?"}
H -->|"单组件"| I["渲染异常<br/>→ Inspector + onPropsChanged/事件名"]
H -->|"整体布局"| J["渲染异常-布局<br/>→ onLayout/Yoga 舍入/字体缩放"]
B -->|"出现后冻结"| K{"AppFreeze?"}
K -->|"mutex::lock 一致"| L["运行期冻屏-死锁<br/>→ 升级 scoped_lock/SafeWeak 修复"]
K -->|"主线程繁忙"| M["运行期冻屏-繁忙<br/>→ 拆任务/异步化 I/O"]
D --> D1["指明文件 + 清缓存 + 对齐配置"]
F --> F1["指明 build-profile/nativeLib + 补 so"]
I --> I1["指明 ComponentInstance + 改事件名/枚举"]
L --> L1["指明修复版本 + 升级"]
附录 C:代码引用格式约定
- ArkTS:
file/path(如src/main/ets/RNOH/JSBundleProvider.ts) - C++:
file/path(如src/main/cpp/RNOH/MountingManagerCAPI.cpp) - 本仓库源码标注
[源码],在线文档标注[在线源码],未读引用标注[待确认],绝不编造
附录 D:相关资料
- 稳定性问题定位指南:[在线源码]
docs/zh-cn/03-调测/RNOH应用稳定性问题定位指南.md— 内存泄漏/卡死/崩溃的日志、trace、符号化方法 - 运行类 FAQ:[在线源码]
docs/zh-cn/03-调测/运行类FAQ.md— 启动闪退、libRNOHApp is undefined、codegen record 找不到 - 定位定界指导类 FAQ:[在线源码]
docs/zh-cn/03-调测/定位定界指导类FAQ.md— ArkUI Inspector、属性/图片/事件排查、整体性能 - 调试调测:[在线源码]
docs/zh-cn/03-调测/调试调测.md— Metro 热加载、Dev Menu、Element Inspector、LogBox、Trace、subscribeToRNOHErrors - OpenHarmony 运行 RN 应用:OpenHarmony 运行 RN 应用 — 开发板 windowRectChange 801、v0.82.30 Text 不渲染、bundle 缓存
- RNOH 与 RN 社区共性问题清单:共性问题清单 — Yoga 舍入、onLayout 死循环、字体缩放、TextInput transparent
- 稳定性历史修复:[在线源码]
docs/zh-cn/05-运维/稳定性历史修复/{0.72,0.77,0.82}稳定性修复汇总/02-应用冻屏.md— 各版本线冻屏根因与修复 - AI 辅助问题定位:[在线源码]
docs/zh-cn/05-运维/AI辅助问题定位指导.md— 借助 AI 结合堆栈快速获得根因 - 配套指南:历史版本问题定位指南 · 三端不一致定位指南
更多推荐

所有评论(0)