稳定性分析方法
稳定性分析的目标不是直接猜测根因,而是基于现象、日志和线程状态逐步缩小范围。对稳定性问题,推荐按“先分类、再取证、后定位”的顺序推进。实际工作中,通常可以分为人工分析和 AI 分析两种方式。
1. 分析方式
1.1 AI 分析
RNOH 场景下,推荐结合稳定性 skill 来做 AI 分析,skill 名称为 rnoh-stability-triage。这个 skill 会优先帮助识别问题画像、匹配历史稳定性修复、结合当前代码验证,并给出升级、补丁回捞或规避建议。
AI 分析的基本使用方式如下:
- 先准备输入信息,至少包括 crash 日志或异常消息、hlog 或 hilog、版本信息,以及问题触发场景。
- 在对话中直接说明当前问题现象,例如启动崩溃、页面退出后崩溃、卡死、OOM、内存持续增长等。
- 明确提供版本范围,例如 0.72、0.77、0.82、0.84,或者直接给出分支名、提交号。
- 把关键日志、堆栈、报错信息贴给 AI,并说明是否希望对照历史稳定性修复汇总一起分析。
- AI 会结合稳定性 skill,先提取问题画像,再去匹配历史修复、检查当前代码保护逻辑,最后输出结论摘要、归因判断和修复建议。
- 如果第一次分析证据不足,再补充符号化结果、更多 hilog、复现步骤或触发时机,继续让 AI 收敛范围。
1.2 人工分析
人工分析的核心是由研发人员自己完成日志阅读、调用链还原、代码比对和根因判断。本文后续关于应用异常退出、应用冻屏、内存异常和资源泄漏的分析方法,主要就是人工分析时可直接参考的步骤。
面对任意稳定性问题,建议优先执行以下步骤:
- 明确现象:判断是进程退出、界面卡死、内存膨胀还是资源长期增长。
- 确定故障类型:先归类到应用异常退出、应用冻屏、内存异常或资源泄漏。
- 收集基础日志:包括 FaultLog、hilog、线程堆栈、trace、内存快照、资源统计信息。
- 锁定关键模块:确认问题更偏向 JS 层、ArkTS 适配层、C++ 框架层还是系统交互层。
- 结合触发阶段分析:重点看启动、页面切换、实例销毁、动画执行、布局提交和长期运行等阶段。
2. 应用异常退出分析
应用异常退出通常分为 CppCrash 和 JS Crash,两类问题的日志入口和定位手段不同。
2.1 CppCrash 分析方法
现象特征
- 应用突然闪退,没有业务层兜底提示。
- FaultLog 中通常能看到崩溃信号和 native 调用栈。
- 常见信号包括 SIGSEGV、SIGABRT、SIGBUS、SIGTRAP。
- 崩溃地址可能为明显异常地址,也可能接近空地址或已释放对象区域。
定位步骤
- 先看 FaultLog 中的 Reason、Signal、崩溃线程和前几帧栈信息。
- 根据信号判断大类,例如 SIGSEGV 多为非法访问,SIGABRT 多为主动终止或异常逃逸,SIGTRAP 多与断言或异常返回路径有关。
- 对关键地址做符号化,还原到函数、文件和代码行。
- 结合汇编或调用链确认是空指针、野指针、UAF、异常终止还是非法状态继续执行。
- 回到代码看生命周期、线程切换和回调解绑是否完整。
常见工具
- DevEco Studio FaultLog 查看。
- addr2line 或等效符号化工具。
- objdump 或等效反汇编工具。
- AddressSanitizer 等地址越界检测工具。
分析重点
- 是否发生在实例销毁后回调继续执行。
- 是否存在裸指针、弱引用缺失或对象释放顺序错误。
- 是否在错误线程访问对象或持有失效上下文。
- 是否存在声明与实现不一致、返回路径不完整或异常边界缺失。
2.2 JS Crash 分析方法
现象特征
- 应用退出,同时日志中伴随 JS 异常信息。
- 常见错误为 TypeError、ReferenceError、RangeError。
- 典型报错包括访问 undefined 属性、调用非函数对象、初始化阶段抛出异常。
定位步骤
- 读取错误类型、错误消息和原始 JS 堆栈。
- 使用 sourcemap 将混淆或构建后的栈还原到源码位置。
- 确认触发点是在页面渲染、对象访问、模块初始化还是异步回调。
- 判断问题属于空值保护缺失、接口契约不一致、数据结构假设错误还是异常未被捕获。
- 进一步检查是否是 JS 异常导致 native 层被动退出,而不是 native 先崩溃。
分析重点
- 变量或对象是否可能为 undefined 或 null。
- 初始化链路中是否把目录、对象、句柄等错误类型传入接口。
- Promise、事件回调或定时任务中的异常是否缺少兜底。
- 发布构建下是否因为混淆、接口映射或平台兼容导致调用失败。
3. 应用冻屏分析
应用冻屏的关键在于进程通常仍然存活,因此不能只看崩溃日志,而要重点看线程状态和调用阻塞位置。
3.1 常见检测类型
- THREAD_BLOCK_6S:主线程长时间卡住。
- APP_INPUT_BLOCK:用户输入处理超时。
- LIFECYCLE_TIMEOUT:生命周期切换超过阈值。
3.2 日志获取方式
- 使用 DevEco Studio 自动收集冻结日志。
- 使用命令行工具导出 faultlogger 目录下的相关文件。
- 结合 trace、hilog 和线程快照一起分析。
3.3 定位步骤
- 先看基础信息:进程号、故障类型、上报时间和前后台状态。
- 找到主线程或关键线程的堆栈,看是否长期停在同一位置。
- 判断阻塞类型:JS 业务耗时、IPC 等待、锁竞争、I/O 阻塞或生命周期切换等待。
- 结合 trace 定位耗时操作是在渲染、布局、模块调用还是页面创建阶段。
- 回到代码确认是否存在持锁回调、同步等待、错误的线程切换或高频重入。
3.4 分析重点
- 主线程是否执行了不应放在前台交互线程中的耗时任务。
- JS 线程与 UI 线程之间是否互相等待。
- 生命周期切换是否依赖同步回调、锁释放或外部资源返回。
- 页面首次创建时是否触发了异常高频的组件构建或布局计算。
4. 内存异常分析
内存异常既可能表现为 OOM,也可能表现为长期泄漏或非法访问。
4.1 OOM 分析方法
现象特征
- 应用突然退出,并伴随内存分配失败信息。
- 内存占用持续增长,最终达到系统上限。
- 在分配大对象时更容易暴露问题。
定位步骤
- 查看崩溃前后的内存曲线和峰值变化。
- 对比历史基线,确认是短时突增还是长期累积。
- 抓取堆快照,对比操作前后的对象存活情况。
- 区分是 JS 堆、Native 堆还是系统共享内存异常增长。
- 结合 Allocation 调用栈定位是谁持续创建对象却没有释放。
4.2 地址越界分析方法
现象特征
- 调试或压测阶段更容易出现。
- 常伴随非法地址访问、UAF、空指针解引用或数组越界。
定位步骤
- 启用地址越界检测工具或 Sanitizer。
- 根据报错地址和访问类型确认是读越界、写越界还是释放后访问。
- 对照调用栈和对象生命周期,检查所有权和释放时序。
- 回溯最近一次对象创建、转移、销毁和回调触发链路。
5. 资源泄漏分析
资源泄漏问题通常不会立即导致退出,但会在长时间运行后引发一系列二次故障。
5.1 常见类型
- 句柄泄漏:文件或系统句柄未关闭。
- 线程泄漏:线程不断创建但未结束或未回收。
- JS 内存泄漏:对象被闭包、监听器或缓存长期持有。
- Native 内存泄漏:实例、调度器、上下文或底层资源未释放。
5.2 定位步骤
- 先看资源统计,确认异常增长的是句柄、线程还是内存。
- 对句柄泄漏,查看泄漏列表和相关系统调用栈。
- 对线程泄漏,按线程名分组统计,识别重复创建模式。
- 对内存泄漏,抓取快照并比较对象是否在页面退出或实例销毁后仍然存活。
- 结合代码审查监听器、回调、定时器、网络请求和模块析构逻辑是否形成释放闭环。
5.3 分析重点
- 是否注册了回调但没有注销。
- 是否创建了线程池、异步任务或调度器但缺少回收。
- 是否在实例销毁后仍持有 JSVM、上下文或 TurboModule 相关引用。
- 是否存在缓存未清理、文件遍历错误或目录对象被误当作文件处理的情况。