鸿蒙高级课程笔记1—应用DFX能力介绍
围绕开发者,构建三方应用和设备从开发到维护全生命周期所必需、有竞争力、有特色的调试调优、定位、维护能力。这些都属于DFX的范围。HarmonyOS为应用、设备、运维(为运行态提供运维能力)、开发阶段提供DFX能力。DFX全景如下:DFX设计是保证产品和应用质量的主要方法。DFX设计主要范围:完备的事件打点接口;方便的事件查看工具;轻量灵活的平台部署;开发者可以通过系统提供的接口监听到系统事件,比如
DFX概述
HarmonyOS中的DFX全称是“Design For X”或“Design For eXcellence”,指的是面向产品生命周期或特定特性(X)的设计。
DFX的核心构成
在HarmonyOS中,DFX专门指那些提升应用和系统非功能性质量的软件设计。它主要包括三个方面:
| 构成部分 | 英文全称 | 核心目标 | 关键工具举例 |
|---|---|---|---|
| DFR | Design for Reliability (可靠性设计) | 确保应用稳定运行,减少崩溃、卡死。 | 通过Performance Analysis Kit提供强大的故障检测与异常处理能力,包括崩溃检测、应用冻屏检测、任务超时检测等。HiCollie & FaultLogger:分别用于检测线程卡死和应用崩溃,并收集相关信息。 |
| DFT | Design for Testability (可测试性设计) | 让应用易于观测、调试和测试。 | 通过Performance Analysis Kit提供HiLog流水日志打印、HiAppEvent事件(故障、行为、安全、统计等)监听、HiSysEvent(提供系统事件埋点接口,用于监控系统状态变化)、HiTrace(用于跟踪分布式场景下跨设备、跨进程的调用链)等全面的基础调试和观测能力 |
| DFPf | Design for Performance (性能设计) | 监控和优化应用性能,如卡顿、丢帧。 | 通过Performance Analysis Kit提供性能检测(应用启动耗时、滑动丢帧等)、性能跟踪(HiTraceMeter用于性能追踪,应用启动耗时、滑动丢帧等性能检测)、分布式调用链跟踪(HiTraceChain) 以及资源使用情况获取(HiDebug)等工具。 |
三个方向更详细的使用介绍参考开发指南—Performance Analysis Kit简介。三个方向细分领域大致如下:
-
故障检测,提供开发者检测应用稳定性故障的能力,包括崩溃检测、地址越界检测、应用冻屏检测、资源泄漏检测、任务超时检测等。
-
功耗检测,提供开发者检测应用功耗异常的能力,如CPU高负载检测。
-
性能检测,提供开发者检测应用性能异常的能力,如应用启动耗时检测、滑动丢帧检测等。
-
日志打印,提供开发者记录和获取流水日志的能力。
-
事件订阅,提供开发者记录故障、行为、安全、统计事件的能力,并订阅系统事件,设置数据处理者以完成数据上传。
-
检测模式,提供开发者检测应用线程耗时调用、元能力资源泄漏等问题的能力。
-
系统调试信息获取,提供开发者获取应用和系统资源使用情况的能力。
-
业务线程超时检测,提供开发者检测业务线程任务执行超时并上报超时事件的能力。
DFX与Performance Analysis Kit关系
官方文档明确指出:“Performance Analysis Kit承载着HarmonyOS DFX子系统面向应用开发者提供的提升应用质量能力集合。”这意味着:DFX是顶层指导思想,Performance Analysis Kit是支撑这套思想落地的具体开发套件。
DFX能力范围
围绕开发者,构建三方应用和设备从开发到维护全生命周期所必需、有竞争力、有特色的调试调优、定位、维护能力。这些都属于DFX的范围。

HarmonyOS为应用、设备、运维(为运行态提供运维能力)、开发阶段提供DFX能力。DFX全景如下:

DFX设计
DFX设计是保证产品和应用质量的主要方法。DFX设计主要范围:
- HiLog日志:日子的内容及位置,这是日子设计的主要问题。
- HiAppEvent应用事件:主要是埋点的上报,埋点的内容和位置是设计的主要问题,包括故障事件、行为事件两部分。 事件埋点接口,提供事件的埋点写入。
- HiTrace Meter跟踪:提供追踪进程轨迹,进行程序性能分析,支持内核ftrace预置埋点和用户态打点。
- HiTrace Chain调用链:主要提供业务流程调用链跟踪的维测,通过业务流程的还原,可以清晰的知道整个业务流程过程中哪些地方发生了问题。
日志设计原则

日志分类查看

日志内容输出原则

HiAppEvent埋点设计原则
完备的事件打点接口;方便的事件查看工具;轻量灵活的平台部署;

系统提供的事件说明
开发者可以通过系统提供的接口监听到系统事件,比如崩溃、卡死事件应用重启后回调相关接口,接口通过AppEventInfo结构返回数据。系统提供的常用事件说明如下图

HiTrace Meter跟踪设计
HiTraceMeter是HarmonyOS/OpenHarmony用于性能追踪的核心工具。它通过在代码中埋点并利用内核的ftrace机制,帮你精确抓取并分析应用、跨设备调用链的性能数据,从而定位卡顿、耗时等瓶颈问题。

HiTrace API介绍及开发
开发者通过ArkTS提供的接口输出开发者需要的trace文件内容。

HiTrace Chain调用链设计
同一个设备内不同的软件层存在相互流转/调用,设备间存在分布式业务需要在各个设备之间流转,这样就给我们的业务故障定位带来非常大的麻烦,我们通常从日志中无法看出哪些日志是属于这个业务的,因此设计了业务链。
业务链就是在整个业务发起方发起的时候生成唯一的业务ID,业务ID会在跨进程、跨设备时随着业务流转。这样就能通过业务ID,将业务所有信息,包括日志、trace、event等关联起来,可以帮助开发者进行定位。

业务ID使用举例如下:

异常处理实践
异常处理框架

应用异常日志查询接口FaultLog
提供QuerySelfFaultLog接口以查询自身故障:
- JS_CRASH:ArkTS程序故障类型
- CPP_CRASH:C++程序故障类型
- APP FREEZE:应用程序卡死故障类型
接口querySelfFaultLog(faultType: FaultType, callback: AsyncCallback<Array<FaultLoglnfo>>):void;接口使用举例如下:

JS_CRASH:ArkTS程序故障类型
日志内容说明如下图:

分析案例举例

CPP_CRASH:C++程序故障类型
日志内容说明如下图:

分析案例举例

混合栈拼接:TS调用C++后崩溃
拼接流程说明

日志内容说明

APP FREEZE:应用程序卡死故障类型
日志内容说明如下图:

分析案例举例

故障恢复实践
概述
我们在系统层去感知故障,包括js Crash、C++ Crash、AppFreeze、kill(某类资源过载后系统会主动杀死应用),对于感知到的故障,对外提供三类接口(异常通知、状态保存、状态恢复)通知应用。
异常通知接口举例

状态保存接口举例

状态恢复直接封装在onCreate方法,在应用启动时如果启动原因是恢复,则走恢复流程。接口举例

应用恢复API全集

应用异常恢复举例

常用工具使用
1、HiLog使用指导
HiLog打印日志(ArkTS)
在应用开发过程中,可在关键代码处输出日志信息。在运行应用后,通过查看日志信息来分析应用执行情况(如应用是否正常运行、代码运行时序、运行逻辑分支是否正常等)。
系统提供不同的API供开发者调用并输出日志信息,即HiLog与Console。两个API在使用时略有差异,本文重点介绍HiLog的用法,Console的具体用法可查看API参考Console。
接口说明
HiLog中定义了DEBUG、INFO、WARN、ERROR、FATAL五种日志级别,并提供了对应的方法输出不同级别的日志,接口如下表所示,具体说明可查阅API参考文档。
| 接口名 | 功能描述 |
|---|---|
| isLoggable(domain: number, tag: string, level: LogLevel) | 在打印日志前调用该接口,检查指定领域标识、日志标识和级别的日志是否可以打印。 |
| debug(domain: number, tag: string, format: string, ...args: any[]) |
输出DEBUG级别日志。仅用于应用/服务调试。 在DevEco Studio的terminal窗口或cmd里,通过命令“hdc shell hilog -b D”设置可打印日志的等级为DEBUG。 |
| info(domain: number, tag: string, format: string, ...args: any[]) | 输出INFO级别日志。表示普通的信息。 |
| warn(domain: number, tag: string, format: string, ...args: any[]) | 输出WARN级别日志。表示存在警告。 |
| error(domain: number, tag: string, format: string, ...args: any[]) | 输出ERROR级别日志。表示存在错误。 |
| fatal(domain: number, tag: string, format: string, ...args: any[]) | 输出FATAL级别日志。表示出现致命错误、不可恢复错误。 |
| setMinLogLevel(level: LogLevel) |
设置应用日志打印的最低日志级别,用于拦截低级别日志打印。 说明:从API version 15开始,支持该接口。 |
| setLogLevel(level: LogLevel, prefer: PreferStrategy) |
设置当前应用程序进程的最低日志级别。可以配置不同的偏好策略。 说明:从API version 21开始,支持该接口。 |
注意
如果设置的日志级别低于全局日志级别,setMinLogLevel()设置不生效。
debug版本应用下,setMinLogLevel()和setLogLevel()函数均不生效。
参数解析
-
domain:用于指定输出日志所对应的业务领域,取值范围为0x0000~0xFFFF,开发者可以根据需要进行自定义。
-
tag:用于指定日志标识,可以为任意字符串,建议标识调用所在的类或者业务行为。tag最多为31字节,超出后会截断。不建议使用中文字符,可能出现乱码或者对齐问题。
-
level:用于指定日志级别。取值见LogLevel。
-
prefer:用于指定偏好策略。取值见PreferStrategy。
-
format:格式字符串,用于日志的格式化输出。日志打印的格式化参数需按照“%{private flag}specifier”的格式打印。
隐私标识符(private flag) 说明 private 表示日志打印结果不可见,输出结果为<private>。 public 表示日志打印结果可见,明文显示参数。 无 缺省值默认为private,日志打印结果不可见。 格式说明符(specifier) 说明 示例 d/i 支持打印number和bigint类型。 123 s 支持打印string、undefined、boolean和null类型。 "123" o/O 支持打印object、undefined和null类型。
从API version 20开始,支持该能力。
{ 'name': "Jack", 'age': 22 } 格式字符串中可以设置多个参数,例如格式字符串为“%{public}s World”,“%{public}s”表示参数类型为string的变参标识,具体取值在args中定义。
debug应用无隐私管控机制,使用上述任意隐私标识符打印日志,都可明文显示参数。
-
args:可以为0个或多个参数,是格式字符串中参数类型对应的参数列表。参数的数量、类型必须与格式字符串中的标识一一对应。
说明
约束与限制
日志最多打印4096字节,超出限制文本将被截断。
HiLog打印日志(C/C++)
在应用开发过程中,可在关键代码处输出日志信息。在运行应用后,通过查看日志信息来分析应用执行情况(如应用是否正常运行、代码运行时序、运行逻辑分支是否正常等)。
HiLog日志系统,提供给系统框架、服务、以及应用,用于打印日志,记录用户操作、系统运行状态等。
接口说明
HiLog中定义了DEBUG、INFO、WARN、ERROR、FATAL五种日志级别,并提供了对应的方法输出不同级别的日志,接口如下表所示,具体说明可查阅API参考文档。
| 方法/宏 | 接口描述 |
|---|---|
| bool OH_LOG_IsLoggable(unsigned int domain, const char *tag, LogLevel level) |
检查指定domain、tag和日志级别的日志是否可以打印。 如果指定日志可以打印则返回true;否则返回false。 |
| int OH_LOG_Print(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt, ...) |
输出指定domain、tag和日志级别的日志,并按照printf格式类型和隐私指示确定需要输出的变参。 返回值大于等于0表示成功,小于0表示失败。 |
| int OH_LOG_PrintMsg(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *message) |
输出指定domain、tag和日志级别的日志字符串。 返回值大于等于0表示成功,小于0表示失败。 说明:从API version 18开始,支持该接口。 |
| int OH_LOG_PrintMsgByLen(LogType type, LogLevel level, unsigned int domain, const char *tag, size_t tagLen, const char *message, size_t messageLen) |
输出指定domain、tag和日志级别的日志字符串,需要指定tag及字符串长度。 返回值大于等于0表示成功,小于0表示失败。 说明:从API version 18开始,支持该接口。 |
| int OH_LOG_VPrint(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt, va_list ap) |
等效于OH_LOG_Print,但是参数列表为va_list。 说明:从API version 18开始,支持该接口。 |
| #define OH_LOG_DEBUG(type, ...) ((void)OH_LOG_Print((type), LOG_DEBUG, LOG_DOMAIN, LOG_TAG, VA_ARGS)) | DEBUG级别写日志,宏封装接口。 |
| #define OH_LOG_INFO(type, ...) ((void)OH_LOG_Print((type), LOG_INFO, LOG_DOMAIN, LOG_TAG, VA_ARGS)) | INFO级别写日志,宏封装接口。 |
| #define OH_LOG_WARN(type, ...) ((void)OH_LOG_Print((type), LOG_WARN, LOG_DOMAIN, LOG_TAG, VA_ARGS)) | WARN级别写日志,宏封装接口。 |
| #define OH_LOG_ERROR(type, ...) ((void)OH_LOG_Print((type), LOG_ERROR, LOG_DOMAIN, LOG_TAG, VA_ARGS)) | ERROR级别写日志,宏封装接口。 |
| #define OH_LOG_FATAL(type, ...) ((void)OH_LOG_Print((type), LOG_FATAL, LOG_DOMAIN, LOG_TAG, VA_ARGS)) | FATAL级别写日志,宏封装接口。 |
| void OH_LOG_SetCallback(LogCallback callback) | 注册函数,注册后可通过LogCallback回调获取本进程的hilog日志。若OH_LOG_IsLoggable接口返回true,则回调函数可获取到该条日志。 |
| void OH_LOG_SetMinLogLevel(LogLevel level) |
设置应用日志打印的最低日志级别,用于拦截低级别日志打印。 说明:从API version 15开始,支持该接口。 |
| void OH_LOG_SetLogLevel(LogLevel level, PreferStrategy prefer) |
设置当前应用程序进程的最低日志级别。可以配置不同的偏好策略。 说明:从API version 21开始,支持该接口。 |
注意
hilog日志接口是非信号安全函数,禁止在信号处理函数中调用非信号安全的hilog日志接口。
如果设置的日志级别低于全局日志级别,OH_LOG_SetMinLogLevel()设置不生效。
debug版本应用下,OH_LOG_SetMinLogLevel()和OH_LOG_SetLogLevel()函数均不生效。
参数解析
-
domain:用于指定输出日志所对应的业务领域,取值范围为0x0000~0xFFFF,开发者可以根据需要进行自定义。
-
tag:用于指定日志标识,可以为任意字符串,建议标识调用所在的类或者业务行为。tag最多为31字节,超出后会截断。不建议使用中文字符,可能出现乱码或者对齐问题。
-
level:用于指定日志级别。取值见LogLevel。
-
prefer:用于指定偏好策略。取值见PreferStrategy。
-
fmt:格式字符串,用于日志的格式化输出。日志打印的格式化参数需按照“%{private flag}specifier”的格式打印。
隐私标识符(private flag) 说明 private 表示日志打印结果不可见,输出结果为<private>。 public 表示日志打印结果可见,明文显示参数。 无 缺省值默认为private,日志打印结果不可见。 格式说明符(specifier) 说明 示例 d/i 支持打印十进制整数类型。 123 s 支持打印char*类型。 "this is a hilog" 格式字符串中可以设置多个参数,例如格式字符串为"%s World",“%s”为参数类型为字符串的变参标识,具体取值在args中定义,格式说明符使用参考printf。
debug应用无隐私管控机制,使用上述任意隐私标识符打印日志,都可明文显示参数。
-
args:可以为0个或多个参数,是格式字符串中参数类型对应的参数列表。参数的数量、类型必须与格式字符串中的标识一一对应。
说明
-
OH_LOG_IsLoggable()和OH_LOG_Print()使用的domain、tag和level应保持一致。
-
OH_LOG_IsLoggable()返回值:如果指定的domain、tag、level日志可以打印则返回true;否则返回false。
debug应用:不做日志级别管控,所有级别日志都能够正常打印出来;
release应用:按照全局日志级别管控,当日志的级别不低于全局日志级别时,才能正常打印出来;
调试过程中,可手动修改日志级别,参考:查看和设置日志级别。
约束与限制
日志最多打印4096字节,超出限制文本将被截断。
开发步骤
1、在CMakeLists.txt中新增libhilog_ndk.z.so链接:
target_link_libraries(entry PUBLIC libhilog_ndk.z.so)
2、在源文件中包含hilog头文件,并定义domain、tag宏:
#include "hilog/log.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏,标识业务领域
#define LOG_TAG "MY_TAG" // 全局tag宏,标识模块日志tag
3、打印日志:
OH_LOG_INFO(LOG_APP, "Failed to visit path.");
// 设置应用日志最低打印级别,设置完成后,低于Warn级别的日志将无法打印
OH_LOG_SetMinLogLevel(LOG_WARN);
OH_LOG_INFO(LOG_APP, "this is an info level log");
OH_LOG_ERROR(LOG_APP, "this is an error level log");
// 设置应用日志PREFER_OPEN_LOG策略的最低打印级别,设置完成后,不低于INFO级别的日志都可打印
OH_LOG_SetLogLevel(LOG_WARN, PREFER_OPEN_LOG);
OH_LOG_INFO(LOG_APP, "this is an another info level log");
OH_LOG_ERROR(LOG_APP, "this is an another error level log");
2、HiAppEvent使用指导
HiAppEvent介绍
简介
HiAppEvent是系统为应用开发者提供的事件打点机制,支持记录应用运行过程中的故障、统计、安全和行为事件,帮助开发者定位问题、分析应用运行情况,统计访问量、用户活跃度、操作习惯以及其他影响用户使用产品的关键因素。
基本概念
打点:记录用户操作引起的变化,提供业务数据信息,供开发、产品、运维分析。
-
事件领域:标识事件的领域,建议设置为业务领域名称,以便于区分不同的业务领域。
-
事件名称:指定事件的名称,建议设置为具体的业务名称,以便于描述实际的业务意义。
-
事件类型:指定事件的类型,支持以下四种类型事件:
- 行为事件:记录用户日常操作行为的事件,例如按钮点击、界面跳转等行为。
- 故障事件:定位和分析应用故障的事件,例如界面卡顿、网络中断等故障。
- 统计事件:统计和度量应用关键行为的事件,例如对使用时长、访问数等的统计。
- 安全事件:记录涉及应用安全行为的事件,例如用户授权等行为。
-
事件参数:指定事件的参数,每个事件可以包含一组参数,建议设置为事件属性或事件发生的上下文信息,以便于描述事件的详细信息。
事件订阅:通过HiAppEvent的接口addWatcher,开发者可以注册监听自己关注的系统事件或应用事件。目的是当订阅的事件发生后,接收事件的回调信息并进行处理。
实现原理
系统事件订阅机制
在当前系统应用沙箱机制下,应用进程仅可以直接访问自己的应用沙箱目录,参考应用沙箱目录。而系统事件信息的存放路径不在应用沙箱目录中,因此无法直接获取。
应用调用HiAppEvent的addWatcher接口订阅系统事件并创建共享目录。当应用进程发生故障时,DFX系统捕获相关信息,生成事件和日志,并写入到共享目录。HiAppEvent监听到事件后,将事件回调给应用。

应用事件订阅机制
应用调用addWatcher接口订阅关注的应用事件后,还需在应用事件发生时,调用write接口进行打点,用来记录应用事件。
HiAppEvent通过事件领域和事件名称关联应用事件,并通过addWatcher接口设置的回调方式将事件回调给应用。

说明
若应用已订阅到相关事件,但在触发回调前应用退出,则未回调的事件会在应用下次启动调用addWatcher后进行回调。例如订阅崩溃事件场景,在应用崩溃退出后,下次启动调用addWatcher后执行事件回调。
约束与限制
-
订阅接口addWatcher是同步接口,涉及IO操作。对于性能有要求的模块,建议将接口的调用放到非主线程。
-
订阅接口addWatcher传入的名称name是唯一的,相同的name,后一次调用会覆盖前一次的订阅。
-
目前鸿蒙应用有普通应用、应用分身、元服务、输入法应用等多种类型,不同类型应用上,系统事件的订阅规格不同。从API version 22开始,HiAppEvent系统事件订阅能力支持输入法应用。具体规格可参见如下表格:
| 系统事件名称 | 是否支持应用分身订阅 | 是否支持元服务订阅 | 是否支持输入法应用订阅 |
|---|---|---|---|
| 崩溃事件 | 支持 | 支持 | 支持 |
| 应用冻屏事件 | 支持 | 支持 | 支持 |
| 资源泄漏事件 | 支持 | 支持 | 支持 |
| 地址越界事件 | 支持 | 不支持 | 支持 |
| 主线程超时事件 | 支持 | 支持 | 支持 |
| 任务执行超时事件 | 支持 | 不支持 | 支持 |
| 应用终止事件 | 支持 | 支持 | 支持 |
| 启动耗时事件 | 不支持 | 支持 | 不支持 |
| 滑动丢帧事件 | 不支持 | 支持 | 不支持 |
| CPU高负载事件 | 不支持 | 不支持 | 支持 |
| 24h功耗器件分解统计事件 | 不支持 | 不支持 | 支持 |
| 音频卡顿事件 | 不支持 | 不支持 | 不支持 |
事件订阅
HiAppEvent提供了事件订阅接口,用于获取应用的事件。
接口说明
API接口使用说明,包括参数使用限制和具体取值范围。请参考@ohos.hiviewdfx.hiAppEvent (应用事件打点)ArkTS API文档。
订阅接口功能介绍:
| 接口名 | 描述 |
|---|---|
| addWatcher(watcher: Watcher): AppEventPackageHolder | 添加应用的事件观察者。 |
| removeWatcher(watcher: Watcher): void | 移除应用的事件观察者。 |
说明
addWatcher接口涉及I/O操作。在对性能敏感的业务场景中,开发者应根据实际需要确定该接口是在主线程还是在子线程中调用。
如果选择在子线程中调用addWatcher,需要确保该子线程在整个接口使用周期内不会被销毁,以免影响接口的正常工作。
可参考多线程并发概述,以实现在子线程中调用接口。
打点接口功能介绍:
| 接口名 | 描述 |
|---|---|
| write(info: AppEventInfo, callback: AsyncCallback<void>): void | 应用事件异步打点方法,使用callback方式作为异步回调。 |
| write(info: AppEventInfo): Promise<void> | 应用事件异步打点方法,使用Promise方式作为异步回调。 |
说明
write接口涉及I/O操作,执行时间通常在毫秒级别。因此,开发者应根据实际业务需求,确定该接口是在主线程还是在子线程中调用。
可参考多线程并发概述,以实现在子线程中调用接口。
事件订阅开发指导
以订阅崩溃事件(系统事件)和按钮点击事件(应用事件)为例,说明开发步骤。
1、新建一个ArkTS应用工程,编辑工程中的“entry > src > main > ets > entryability > EntryAbility.ets”文件,导入所需的依赖模块:
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';
2、编辑工程中的“entry > src > main > ets > entryability > EntryAbility.ets” 文件,在onCreate函数中添加对崩溃事件、按钮点击事件的订阅。
订阅崩溃事件,采用OnReceive类型观察者的订阅方式,观察者接收到事件后会立即触发OnReceive()回调。编辑“EntryAbility.ets”文件,定义OnReceive类型观察者相关方法:
hiAppEvent.addWatcher({
// 开发者可以自定义观察者名称,系统会使用名称来标识不同的观察者
name: 'AppCrashWatcher',
// 订阅过滤条件,这里是订阅了系统事件中的崩溃事件
appEventFilters: [
{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_CRASH]
}
],
// 实现onReceive回调,监听到事件后实时回调
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
hilog.info(0x0000, 'testTag', 'AppEvents HiAppEvent success to read event with onReceive callback from ArkTS');
hilog.info(0x0000, 'testTag', `domain=${domain}`);
for (const eventGroup of appEventGroups) {
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventName=${eventGroup.name}`);
for (const eventInfo of eventGroup.appEventInfos) {
// 开发者可以获取到崩溃事件发生的时间戳
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventInfo.params.time=${JSON.stringify(eventInfo.params['time'])}`);
// 开发者可以获取到崩溃应用的包名
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventInfo.params.bundle_name=${JSON.stringify(eventInfo.params['bundle_name'])}`);
// 开发者可以获取到崩溃事件发生时的故障日志文件
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventInfo.params.external_log=${JSON.stringify(eventInfo.params['external_log'])}`);
}
}
}
});
订阅按钮点击事件,采用OnTrigger类型观察者的订阅方式。需满足triggerCondition设置的条件,才能触发OnTrigger()回调。编辑“EntryAbility.ets”文件,定义OnTrigger类型观察者相关方法:
hiAppEvent.addWatcher({
// 开发者可以自定义观察者名称,系统会使用名称来标识不同的观察者
name: 'ButtonClickWatcher',
// 开发者可以订阅感兴趣的应用事件,此处是订阅了按钮事件
appEventFilters: [{ domain: 'button' }],
// 开发者可以设置订阅回调触发的条件,此处是设置为事件打点数量满足1个
triggerCondition: { row: 1 },
// 开发者可以自行实现订阅回调函数,以便对订阅获取到的事件打点数据进行自定义处理
onTrigger: (curRow: number, curSize: number, holder: hiAppEvent.AppEventPackageHolder) => {
// 如果返回的holder对象为null,表示订阅过程发生异常。因此,在记录错误日志后直接返回
if (holder == null) {
hilog.error(0x0000, 'testTag', 'AppEvents HiAppEvent holder is null');
return;
}
hilog.info(0x0000, 'testTag', 'AppEvents HiAppEvent success to read event with onTrigger callback from ArkTS');
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent onTrigger: curRow=%{public}d, curSize=%{public}d`, curRow, curSize);
let eventPkg: hiAppEvent.AppEventPackage | null = null;
// 根据设置阈值大小(默认为1条事件)去获取订阅事件包,直到将订阅数据全部取出
// 返回的事件包对象为null,表示当前订阅数据已被全部取出,此次订阅回调触发结束
while ((eventPkg = holder.takeNext()) != null) {
// 开发者可以对事件包中的事件打点数据进行自定义处理,此处是将事件打点数据打印在日志中
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventPkg.packageId=%{public}d`, eventPkg.packageId);
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventPkg.row=%{public}d`, eventPkg.row);
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventPkg.size=%{public}d`, eventPkg.size);
for (const eventInfo of eventPkg.data) {
hilog.info(0x0000, 'testTag', `AppEvents HiAppEvent eventPkg.info=%{public}s`, eventInfo);
}
}
}
});
3、写代码触发事件
编辑工程中的“entry > src > main > ets > pages > Index.ets” 文件,导入依赖模块。编辑工程中的“entry > src > main > ets > pages > Index.ets” 文件,新增“WatchAppCrash ArkTS&C++”按钮触发崩溃事件;新增“writeEvent ArkTS”按钮,在按钮点击的函数中进行事件打点。
Button('WatchAppCrash ArkTS&C++')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('80%')
.height('5%')
.onClick(() => {
// 在按钮点击函数中构造一个crash场景,触发崩溃事件
let result: object = JSON.parse('');
})
Button('writeEvent ArkTS')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('80%')
.height('5%')
.onClick(() => {
// 在按钮点击函数中进行事件打点,以记录按钮点击事件
let eventParams: Record<string, number> = {'clickTime': 100};
let eventInfo: hiAppEvent.AppEventInfo = {
// 事件领域定义
domain: 'button',
// 事件名称定义
name: 'click',
// 事件类型定义
eventType: hiAppEvent.EventType.BEHAVIOR,
// 事件参数定义
params: eventParams,
};
hiAppEvent.write(eventInfo).then(() => {
hilog.info(0x0000, 'testTag', `AppEvents writeEvent ArkTS success`);
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', `AppEvents HiAppEvent err.code: ${err.code}, err.message: ${err.message}`);
});
})
3、HiTraceMeter使用指导
使用HiTraceMeter跟踪性能(ArkTS)
简介
HiTraceMeter提供系统性能打点接口。开发者在关键代码位置调用这些API,能够有效跟踪进程轨迹,查看系统和应用性能。
接口说明
性能打点跟踪接口由HiTraceMeter模块提供,详细API请参考@ohos.hiTraceMeter (性能打点)。
| 接口名 | 描述 |
|---|---|
| hiTraceMeter.startSyncTrace(level: HiTraceOutputLevel, name: string, customArgs?: string): void |
开启一个同步时间片跟踪事件,分级控制跟踪输出。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.finishSyncTrace(level: HiTraceOutputLevel): void |
结束一个同步时间片跟踪事件,分级控制跟踪输出。 level必须与流程开始的startSyncTrace()对应参数值保持一致。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.startAsyncTrace(level: HiTraceOutputLevel, name: string, taskId: number, customCategory: string, customArgs?: string): void |
开启一个异步时间片跟踪事件,分级控制跟踪输出。 taskId是trace中用来表示关联的ID,如果有多个name相同的任务并行执行,则开发者每次调用startAsyncTrace()时,传入的taskId需不同;如果具有相同name的任务是串行执行的,则taskId可以相同。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.finishAsyncTrace(level: HiTraceOutputLevel, name: string, taskId: number): void |
结束一个异步时间片跟踪事件,分级控制跟踪输出。 level、name和taskId必须与流程开始的startAsyncTrace()对应参数值保持一致。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.traceByValue(level: HiTraceOutputLevel, name: string, count: number): void |
整数跟踪事件,分级控制跟踪输出。 name和count两个参数分别用来标记一个跟踪的整数变量名及整数值。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.isTraceEnabled(): boolean |
判断当前是否开启应用trace捕获。 使用hitrace命令行工具等方式开启采集时返回true。未开启采集或停止采集后返回false,此时调用HiTraceMeter性能跟踪打点接口无效。 说明:从API version 19开始,支持该接口。 |
| hiTraceMeter.registerTraceListener(callback: TraceEventListener): number |
注册应用trace捕获开关通知回调,使用callback异步回调。 注册成功后,立即执行一次回调函数,后续回调函数由应用trace捕获开关状态变化触发执行。 说明:从API version 22开始,支持该接口。 |
| hiTraceMeter.unregisterTraceListener(index: number): number |
注销应用trace捕获开关通知回调。 说明:从API version 22开始,支持该接口。 |
注意
用户态trace格式使用竖线 | 作为分隔符,所以通过HiTraceMeter接口传递的字符串类型参数应避免包含该字符,防止trace解析异常。
接口分类
HiTraceMeter打点接口分为三类:同步时间片跟踪、异步时间片跟踪和整数跟踪。HiTraceMeter接口实现均为同步,同步和异步针对的是被跟踪的业务。同步业务使用同步时间片跟踪接口,异步业务使用异步时间片跟踪接口。HiTraceMeter打点接口可与HiTraceChain一起使用,进行跨设备、跨进程或跨线程的打点关联与分析。
接口使用场景
-
同步时间片跟踪接口
用于顺序执行的打点场景,需按序成对使用startSyncTrace()接口和finishSyncTrace()接口,否则会导致trace文件在smartperf等可视化工具上显示异常。
-
异步时间片跟踪接口
在异步操作执行前调用startAsyncTrace()接口进行开始打点,在异步操作完成后调用finishAsyncTrace()接口进行结束打点。
解析trace时,通过name和taskId参数识别不同的异步跟踪。这两个接口必须按序成对使用,并传入相同的name和taskId。
不同的异步流程中应使用不同的name和taskId,但在异步跟踪流程不会同时发生的情况下,可以使用相同的name和taskId。
调用错误会导致trace文件在smartperf等可视化工具上显示异常。
-
整数跟踪接口
用于跟踪整数变量。整数值变动时调用traceByValue()接口,可在smartperf的泳道图中观察变动情况。由于从开始采集到首次打点存在时间差,这段时间的数值无法查看。
参数解析
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| level | enum | 是 |
跟踪输出级别。低于系统阈值的跟踪将不会被输出。 log版本阈值默认为INFO,nolog版本阈值默认为COMMERCIAL。 |
| name | string | 是 | 要跟踪的任务名称或整数变量名称。 |
| taskId | number | 是 | 用来表示关联的ID,如果有多个name相同的任务并行执行,则开发者每次调用startAsyncTrace()时,传入的taskId需不同。 |
| count | number | 是 | 整数变量的值。 |
| customCategory | string | 是 |
自定义聚类名称,用于聚合同一类异步跟踪打点。 若不需要聚类,可传入一个空字符串。 |
| customArgs | string | 否 |
自定义键值对,若有多组键值对,使用逗号进行分隔,例"key1=value1,key2=value2"。 若不需要该参数,可不传入该参数或传入一个空字符串。 |
| callback | (boolean) => void | 是 | 注册的回调函数。 |
| index | number | 是 | registerTraceListener()返回的回调索引。 |
说明
用户态trace总长度限制为512字符,超过部分将会被截断。建议name、customCategory和customArgs三个字段的总长度不超过420字符,以避免trace被截断。
开发步骤
以下为一个使用HiTraceMeter打点接口的ArkTS应用示例。
步骤一:创建项目
1、在DevEco Studio中新建工程,选择“Empty Ability”。
2、编辑“entry > src > main > ets > pages > Index.ets”文件,在文本点击事件处理业务中使用HiTraceMeter性能跟踪打点接口。
步骤二:采集trace信息并查看
1、在DevEco Studio Terminal窗口中执行以下命令,开启应用的trace捕获。
PS D:\xxx\xxx> hdc shell
$ hitrace --trace_begin app
2、单击DevEco Studio界面上的运行按钮,启动应用。点击应用界面的“Hello World”文本,执行包含HiTraceMeter打点的业务逻辑。然后执行如下命令抓取trace数据,并使用“myTest”关键字过滤trace数据(示例打点接口传递的name字段前缀均为“myTest”)。
$ hitrace --trace_dump | grep myTest
trace数据举例:e.myapplication-39945 ( 39945) [010] .... 347921.342267: tracing_mark_write: S|39945|H:myTestAsyncTrace|1001|M62|categoryTest|key=value
每一行trace数据格式说明:tracing_mark_write为打点事件类型,应用程序中调用HiTraceMeter接口打点使用的均为此事件。打点事件类型前面的数据分别为线程名-线程ID、进程ID、CPU和打点时间(从开机到当前的时间,单位为秒);打点事件类型后面的数据可查看用户态trace格式。
步骤三:停止采集trace
1、执行以下命令,停止应用的trace捕获。
$ hitrace --trace_finish
2、再次点击应用界面的“Hello World”文本,此时应用trace捕获已关闭,isTraceEnabled()接口返回false。在DevEco Studio Log窗口使用关键字“not enabled”进行过滤,会打印如下日志。
myTraceTest running, trace is not enabled
说明
log版本在使用hitrace --trace_finish命令停止采集后会自动拉起快照模式,打开trace捕获,此时isTraceEnabled()接口返回true,不会打印上述日志。
使用HiTraceMeter跟踪性能(C/C++)
简介
HiTraceMeter提供系统性能打点接口。开发者在关键代码位置调用这些API,能够有效跟踪进程轨迹,查看系统和应用性能。
接口说明
性能打点跟踪接口由HiTraceMeter模块提供,详细API请参考trace.h。
| 方法 | 接口描述 |
|---|---|
| void OH_HiTrace_StartTraceEx(HiTrace_Output_Level level, const char* name, const char* customArgs) |
开启一个同步时间片跟踪事件,分级控制跟踪输出。 说明:从API version 19开始,支持该接口。 |
| void OH_HiTrace_FinishTraceEx(HiTrace_Output_Level level) |
结束一个同步时间片跟踪事件,分级控制跟踪输出。 level必须与流程开始的OH_HiTrace_StartTraceEx()对应参数值保持一致。 说明:从API version 19开始,支持该接口。 |
| void OH_HiTrace_StartAsyncTraceEx(HiTrace_Output_Level level, const char* name, int32_t taskId, const char* customCategory, const char* customArgs) |
开启一个异步时间片跟踪事件,分级控制跟踪输出。 taskId是trace中用来表示关联的ID,如果有多个name相同的任务并行执行,则开发者每次调用OH_HiTrace_StartAsyncTraceEx()时,传入的taskId需不同;如果具有相同name的任务是串行执行的,则taskId可以相同。 说明:从API version 19开始,支持该接口。 |
| void OH_HiTrace_FinishAsyncTraceEx(HiTrace_Output_Level level, const char* name, int32_t taskId) |
结束一个异步时间片跟踪事件,分级控制跟踪输出。 level、name和taskId必须与流程开始的OH_HiTrace_StartAsyncTraceEx()对应参数值保持一致。 说明:从API version 19开始,支持该接口。 |
| void OH_HiTrace_CountTraceEx(HiTrace_Output_Level level, const char* name, int64_t count) |
整数跟踪事件,分级控制跟踪输出。 name、count两个参数分别用来标记一个跟踪的整数变量名及整数值。 说明:从API version 19开始,支持该接口。 |
| bool OH_HiTrace_IsTraceEnabled(void) |
判断当前是否开启应用trace捕获。 使用hitrace命令行工具等方式开启采集时返回true,未开启采集或停止采集后返回false,此时调用HiTraceMeter性能跟踪打点接口无效。 说明:从API version 19开始,支持该接口。 |
| int32_t OH_HiTrace_RegisterTraceListener(OH_HiTrace_TraceEventListener callback) |
注册应用trace捕获开关通知回调,使用callback异步回调。 注册成功后,立即执行一次回调函数,后续回调函数由应用trace捕获开关状态变化触发执行。 说明:从API version 22开始,支持该接口。 |
| int32_t OH_HiTrace_UnregisterTraceListener(int32_t index); |
注销应用trace捕获开关通知回调。 说明:从API version 22开始,支持该接口。 |
注意
用户态trace格式使用竖线 | 作为分隔符,所以通过HiTraceMeter接口传递的字符串类型参数应避免包含该字符,以防止trace解析异常。
接口分类
HiTraceMeter打点接口主要分为三类:同步时间片跟踪接口、异步时间片跟踪接口和整数跟踪接口。HiTraceMeter接口实现均为同步,同步和异步针对的是被跟踪的业务。同步业务使用同步时间片跟踪接口,异步业务使用异步时间片跟踪接口。HiTraceMeter打点接口可与HiTraceChain一起使用,进行跨设备、跨进程或跨线程的打点关联与分析。
接口使用场景
-
同步时间片跟踪接口
用于顺序执行的打点场景,需按序成对使用OH_HiTrace_StartTraceEx()接口和OH_HiTrace_FinishTraceEx()接口,否则会导致trace文件在smartperf等可视化工具上显示异常。
-
异步时间片跟踪接口
在异步操作执行前调用OH_HiTrace_StartAsyncTraceEx()接口进行开始打点,在异步操作完成后调用OH_HiTrace_FinishAsyncTraceEx()接口进行结束打点。
解析trace时,通过name和taskId参数识别不同的异步跟踪。所以这两个接口必须按序成对使用,并传入相同的name和taskId。
不同的异步流程中应使用不同的name和taskId,但在异步跟踪流程不会同时发生的情况下,可以使用相同的name和taskId。
调用错误会导致trace文件在smartperf等可视化工具上显示异常。
-
整数跟踪接口
用于跟踪整数变量。整数值变动时调用OH_HiTrace_CountTraceEx()接口,可在smartperf的泳道图中观察变动情况。由于从开始采集到首次打点存在时间差,这段时间的数值无法查看。
参数解析
| 参数名 | 类型 | 说明 |
|---|---|---|
| level | enum |
跟踪输出级别,低于系统阈值的跟踪将不会被输出。 log版本阈值为HITRACE_LEVEL_INFO,nolog版本阈值为HITRACE_LEVEL_COMMERCIAL。 |
| name | const char* | 要跟踪的任务名称或整数变量名称。 |
| taskId | int32_t | 用来表示关联的ID,如果有多个name相同的任务并行执行,则开发者每次调用OH_HiTrace_StartAsyncTraceEx()时,传入的taskId需不同。 |
| count | int64_t | 整数变量的值。 |
| customCategory | const char* |
自定义聚类名称,用于聚合同一类异步跟踪打点。 若不需要聚类,可传入一个空字符串。 |
| customArgs | const char* |
自定义键值对,若有多组键值对,使用逗号进行分隔,例"key1=value1,key2=value2"。 若不需要该参数,可传入一个空字符串。 |
| callback | void (*)(bool) | 注册的回调函数。 |
| index | int32_t | OH_HiTrace_RegisterTraceListener()返回的回调索引。 |
说明
用户态trace总长度限制为512字符,超过部分将会被截断。建议name、customCategory和customArgs三个字段的总长度不超过420字符,以避免trace被截断。
开发步骤
以下为一个使用HiTraceMeter打点接口的Native C++应用示例。
步骤一:创建项目
1、在DevEco Studio中新建工程,选择“Native C++”。
2、在“entry > src > main > cpp > CMakeLists.txt”文件中新增libhitrace_ndk.z.so和libhilog_ndk.z.so动态链接库,完整的文件内容如下。
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(HiTraceChainTest03)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhitrace_ndk.z.so libhilog_ndk.z.so)
3、编辑“entry > src > main > cpp > napi_init.cpp”文件,在Add函数中调用HiTraceMeter NDK_C接口进行性能打点跟踪,完整的示例代码参考官方课程链接。
步骤二:采集trace信息并查看
1、在DevEco Studio Terminal窗口中执行以下命令,开启应用的trace捕获。
PS D:\xxx\xxx> hdc shell
$ hitrace --trace_begin app
2、单击DevEco Studio界面上的运行按钮,启动应用。点击应用界面的“Hello World”文本,执行包含HiTraceMeter打点的业务逻辑。然后执行如下命令抓取trace数据,并使用“myTest”关键字过滤trace数据(示例打点接口传递的name字段前缀均为“myTest”)。
$ hitrace --trace_dump | grep myTest
trace数据举例:e.myapplication-39945 ( 39945) [010] .... 347921.342267: tracing_mark_write: S|39945|H:myTestAsyncTrace|1001|M62|categoryTest|key=value
每一行trace数据格式说明:tracing_mark_write为打点事件类型,应用程序中调用HiTraceMeter接口打点使用的均为此事件。打点事件类型前面的数据分别为线程名-线程ID、进程ID、CPU和打点时间(从开机到当前的时间,单位为秒);打点事件类型后面的数据可查看用户态trace格式。
步骤三:停止采集trace
1、执行以下命令,停止应用的trace捕获。
$ hitrace --trace_finish
2、再次点击应用界面的“Hello World”文本,此时应用trace捕获已关闭,isTraceEnabled()接口返回false。在DevEco Studio Log窗口使用关键字“not enabled”进行过滤,会打印如下日志。
myTraceTest running, trace is not enabled
说明
log版本在使用hitrace --trace_finish命令停止采集后会自动拉起快照模式,打开trace捕获,此时isTraceEnabled()接口返回true,不会打印上述日志。
查看HiTraceMeter日志
通过DevEco Studio可视化界面查看
使用DevEco Studio Profiler工具可以可视化展示HiTraceMeter日志内容,分析应用或服务的CPU使用率和线程运行状态,查看指定时间段内程序在CPU上的执行耗时。具体使用指导请参考CPU活动分析。
通过命令行工具查看
-
根据hdc命令行工具指导,完成hdc环境准备,确保可以使用“hdc shell”命令正常连接设备。
-
在DevEco Studio Terminal窗口或主机命令行窗口执行“hdc shell”命令连接设备,然后在设备上执行hitrace命令,开启HiTraceMeter日志抓取服务。
PS D:\xxx\xxx> hdc shell $ hitrace --trace_begin app -
在设备上运行包含HiTraceMeter打点的相关程序。
-
使用hitrace命令行工具采集文本格式trace信息,将trace文件保存到设备/data/local/tmp/路径下,具体流程可参考快照模式捕获文本格式trace。
-
退出设备,进入主机,导出设备中的HiTraceMeter文本日志到当前目录(下面命令中的trace.ftrace根据采集时设置的文件名修改)。
$ exit PS D:\xxx\xxx> hdc file recv /data/local/tmp/trace.ftrace ./ -
在HiTraceMeter文本日志中搜索打点名称等关键字,查看打点是否成功。
-
HiTraceMeter支持文本日志的可视化分析。
-
在DevEco Studio中导入日志进行分析。
在DevEco Studio Profiler的会话区选择“Open File”,将HiTraceMeter文本日志导入DevEco Studio。
具体分析可参考CPU活动分析文档。
-
通过Smartperf_Host工具进行分析。
-
用户态trace格式说明
未整理,请参考官方课程链接。
4、HiTraceChain使用指导
简介
HiTraceChain是基于云计算分布式跟踪调用链思想,在端侧业务流程(涉及跨线程、跨进程、跨设备)中的一种轻量级实现。hiTraceChain在业务控制面流程中,生成和传递唯一跟踪标识,在业务流程中输出的各类信息中(包括应用事件、系统时间、日志等)记录该跟踪标识。在调试、问题定位过程中,开发者可以通过该唯一跟踪标识将本次业务流程端到端的各类信息快速关联起来。hiTraceChain为开发者提供业务流程调用链跟踪的维测接口,帮助开发者迅速获取指定业务流程调用链的运行日志,定位跨设备/跨进程/跨线程的故障问题。
基本概念
chainId:分布式跟踪标识,属于HiTraceId的一部分,用于标识当前跟踪的业务流程。
接口说明
分布式跟踪接口由hiTraceChain模块提供,详细API请参考分布式跟踪API参考。
分布式跟踪接口功能介绍:
| 接口名 | 描述 |
|---|---|
| hiTraceChain.begin(name: string, flags?: number = HiTraceFlag.DEFAULT) | 开始跟踪。 |
| hiTraceChain.end(id: HiTraceId) | 结束跟踪。 |
开发步骤
以构造单次应用事件打点的业务说明分布式调用链的使用方法。
-
新建一个ets应用工程,编辑工程中的“entry > src > main > ets > pages > index.ets” 文件,添加一个按钮,完整示例代码参考官方课程。
-
点击DevEco Studio界面中的运行按钮,运行应用工程,然后在应用界面中点击“Start writing an app event”按钮,触发业务逻辑。
-
在Log窗口查看分布式跟踪的相关信息,使用“.[([0-9a-zA-Z]{15}).].*”过滤日志,查看该业务的分布式跟踪信息。hap进程号为“21519”,点击按钮触发的系统事件打点业务涉及到“21519”与“23924”两个线程,通过值为“a92ab94c18e1341”的chainId可以有效跟踪涉及该业务的所有线程的日志信息。
跨进程/跨设备分布式跟踪说明
跨进程/跨设备分布式跟踪依赖于HarmonyOS各模块相应业务接口的napi实现是否存在进程及设备之间的通信调用。
错误管理与应用恢复
错误管理
场景介绍
当应用的代码存在规范问题或错误时,会在运行中产生异常和错误,如应用未捕获异常等。在错误产生后,应用会异常退出。错误日志通常会保存在用户本地存储设备中,不方便开发者定位问题。所以,应用开发者可以使用错误管理的接口,在应用退出前,及时将相关错误及日志上报到开发者的服务平台来定位问题。
使用errorManager接口监听异常和错误后,应用不会退出,建议在回调函数执行完后,增加同步退出操作,如果只是为了获取错误日志,建议使用HiAppEvent订阅事件。
接口说明
应用错误管理接口由@ohos.app.ability.errorManager (错误管理模块)提供,使用接口能力前需注册错误观测器,开发者可以通过import引入,详见开发示例。
错误管理接口功能介绍:
| 接口名称 | 说明 |
|---|---|
| on(type: "error", observer: ErrorObserver): number | 注册错误监听接口,当系统监测到应用异常时会回调该监听。该接口为同步接口,返回值为注册的监听对象对应的序号。 |
| off(type: "error", observerId: number, callback: AsyncCallback<void>): void | 以callback的形式解除注册监听,传入的number为之前注册监听时返回的序号。 |
| off(type: "error", observerId: number): Promise<void> | 以Promise的形式解除注册监听,传入的number为之前注册监听时返回的序号。 |
| on(type: 'globalErrorOccurred', observer: GlobalObserver): void |
注册进程错误监听接口,当系统监测到应用异常时会回调该监听,该接口为同步接口,即一次注册,全局监听。(推荐使用) 说明:从API version 18开始,支持该接口。 |
| off(type: 'globalErrorOccurred', observer?: GlobalObserver): void |
以callback的形式解除注册监听。(推荐使用) 说明:从API version 18开始,支持该接口。 |
| on(type: 'globalUnhandledRejectionDetected', observer: GlobalObserver): void |
注册进程错误监听接口,当系统监测到应用promise异常时会回调该监听,该接口为同步接口,即一次注册,全局监听。(推荐使用) 说明:从API version 18开始,支持该接口。 |
| off(type: 'globalUnhandledRejectionDetected', observer?: GlobalObserver): void |
以callback的形式解除注册监听。(推荐使用) 说明:从API version 18开始,支持该接口。 |
| on(type: 'loopObserver', timeout: number, observer: LoopObserver): void |
注册主线程消息处理耗时监听器,当系统监测到应用主线程事件处理超时时会回调该监听。 只能在主线程调用,多次注册后,后一次的注册会覆盖前一次的。 |
| off(type: 'loopObserver', observer?: LoopObserver): void | 以LoopObserver的形式解除应用主线程消息处理耗时监听。 |
| on(type: 'freeze', observer: FreezeObserver): void | 注册应用主线程freeze监听。只能在主线程调用,重复注册后,后一次的注册会覆盖前一次的。 |
| off(type: 'freeze', observer?: FreezeObserver): void |
以FreezeObserver的形式解除应用主线程消息处理耗时监听。 说明:从API version 18开始,支持该接口。 |
| setDefaultErrorHandler(defaultHandler?: ErrorHandler): ErrorHandler |
仅允许在主线程调用,发生JS_CRASH异常时,支持链式回调,返回值为上一次注册的处理器。 说明:从API version 21开始,支持该接口。 |
| 当采用callback作为异步回调时,可以在callback中进行下一步处理。 | |
| 当采用Promise对象返回时,可以在Promise对象中类似地处理接口返回值,具体结果码说明见解除注册结果码。 |
错误监听(ErrorObserver)接口功能介绍:
| 接口名称 | 说明 |
|---|---|
| onUnhandledException(errMsg: string): void | 系统回调接口,应用注册后,当应用产生未捕获的异常时的回调。 |
| onException?(errObject: Error): void | 系统回调接口,应用注册后,当应用产生异常上报js层时的回调。 |
应用主线程监听(LoopObserver)接口功能介绍:
| 接口名称 | 说明 |
|---|---|
| onLoopTimeOut?(timeout: number): void | 系统回调接口,应用注册后,当应用主线程处理事件超时的回调。 |
解除注册结果码
| 结果码 | 原因 |
|---|---|
| 0 | 正常返回 |
| -1 | 传入的number参数不存在 |
| -2 | 参数错误 |
开发示例
注意
建议在异常回调函数处理的最后,增加同步退出操作,以避免多次异常回调。
开发代码参考课程代码。
应用恢复
场景介绍
应用在运行中不可避免会产生一些非预期的行为,如运行时抛出未处理的异常和错误,违反框架的调用/运行约束等。
系统默认对异常的处理方式为进程退出,如果应用使用过程中产生了用户数据,直接退出可能会导致用户工作中断,数据丢失。
如果应用在AbilityStage中使能应用恢复功能,并对临时数据进行保存,应用非预期退出后的下一次启动会恢复先前的状态和数据,给用户更连贯的使用体验。这里状态包括应用的页面栈以及onSaveState接口中保存的数据。
接口说明
应用故障恢复接口由appRecovery模块提供,开发者可以通过import引入,详见开发步骤。
应用恢复接口功能介绍
| 接口名称 | 说明 |
|---|---|
| enableAppRecovery(restart?: RestartFlag, saveOccasion?: SaveOccasionFlag, saveMode?: SaveModeFlag) : void | 使能应用恢复功能。 |
| saveAppState(): boolean | 主动保存当前应用中支持恢复的UIAbility的状态。 |
| restartApp(): void | 重启当前进程,并启动由setRestartWant指定的UIAbility,如果未指定,将重新拉起处于前台且支持恢复的UIAbility。 |
| saveAppState(context?: UIAbilityContext): boolean | 主动保存由Context指定的UIAbility状态。 |
| setRestartWant(want: Want): void | 设置主动调用restartApp以及RestartFlag不为NO_RESTART时重启的UIAbility(want的abilityName属性可设置为UIAbility的名称)。该UIAbility必须在同一个包名下。 |
由于上述接口可能在故障处理时使用,所以不会返回异常,需要开发者熟悉使用的场景。具体其各参数定义详见参数说明。
enableAppRecovery:需要在应用初始化阶段调用,比如AbilityStage的onCreate调用。调用该接口后,应用恢复时将按首个支持恢复的UIAbility进行恢复。
saveAppState:调用后框架会回调当前进程中所有支持恢复的UIAbility的onSaveState方法。如果在onSaveState方法中同意保存数据,则会将相关数据及UIAbility的页面栈持久化到应用的本地缓存。如果需要保存指定UIAbility,则需要指定UIAbility对应的Context。
setRestartWant:指定由appRecovery发起重启的UIAbility。
restartApp:调用后框架会杀死当前应用进程,并重新拉起由setRestartWant指定的UIAbility,其中启动原因为APP_RECOVERY。
API 9以及未使用setRestartWant指定UIAbility的场景,会拉起最后一个支持恢复且在前台的UIAbility,如果当前前台的UIAbility不支持恢复,则应用表现闪退。
如果重启的UIAbility存在已经保存的状态,这些状态数据会在UIAbility的OnCreate生命周期回调的want参数中作为wantParam属性传入。两次重启的间隔应大于一分钟,一分钟之内重复调用此接口只会退出应用不会重启应用。自动重启的行为与主动重启一致。
应用恢复状态管理示意
从API 10起,应用恢复的场景不仅局限于异常时自动重启。所以需要理解应用何时会加载恢复的状态。
简而言之,如果应用任务的上次退出不是由用户发起的,且应用存在用于恢复的状态,应用下一次由用户拉起时的启动原因会被设为APP_RECOVERY,并清理该任务的恢复状态。
应用恢复状态标识会在状态保存接口主动或者被动调用时设置。在应用正常退出或者应用异常退出重启后,该状态会被清理。正常退出目前包括用户按后退键退出以及用户清理最近任务。

应用卡死的状态保存及恢复
API 10开始支持应用卡死时的状态保存。JsError故障时,onSaveState接口在主线程进行回调。对于AppFreeze故障,主线程可能处于卡死的状态,onSaveState会在非主线程进行回调。其主要流程如下图:

由于卡死时的回调不在JS线程上执行,onSaveState回调中的代码建议不要使用import进来的Native动态库,禁止访问主线程创建的thread_local对象。
框架故障管理流程示意
故障管理是应用提升用户体验的重要手段。应用程序框架为开发者提供了故障监听、故障恢复、以及故障查询三种方式来管理应用的故障。
-
故障监听指的是通过errorManager注册ErrorObserver,监听故障的发生,并通知到监听方。
-
故障恢复指的是appRecovery,及故障发生后,将应用重启恢复到故障之前的状态。
-
故障查询指的是faultLogger通过其查询接口获取当前的故障信息。
下图中并没有标记faultLogger的调用时机,开发者可以根据应用启动时传入的LastExitReason来决定是否调用faultLogger查询上次的故障信息。

这里建议应用开发者使用errorManager对应用的异常进行处理,处理完成后开发者可以选择调用状态保存接口并主动重启应用。
如果开发者没有注册ErrorObserver也没有使能应用恢复,则按照系统的默认逻辑执行进程退出。用户可以选择从启动器再次打开应用。
如果开发者使能应用恢复,框架会首先检查当前故障是否支持状态保存以及开发者是否配置了状态保存,如果支持则会回调UIAbility的onSaveState的接口。最后重启应用。
应用故障管理接口支持场景
通常的故障类型有JS程序Crash、应用程序卡死、C++程序Crash。Crash故障时应用一般都会被关闭。Freeze故障为应用无响应卡屏场景。应用上层无需关注故障类型,底层恢复框架会根据故障类型来实现不同场景的故障管理。
| 故障名称 | 故障监听 | 状态保存 | 自动重启 | 日志查询 |
|---|---|---|---|---|
| JS_CRASH | 支持 | 支持 | 支持 | 支持 |
| APP_FREEZE | API18及以上支持 | 支持 | 支持 | 支持 |
| CPP_CRASH | 不支持 | 不支持 | 不支持 | 支持 |
这里状态保存指的是故障时状态保存,对于应用卡死场景,开发者可以采用定时保存状态或者在UIAbility切入后台后自动保存的方式最大限度的保护用户数据。
开发步骤
使能开启自恢复特性
开发者需要在应用模块初始化时使能appRecovery功能。下面为示例的AbilityStage。
import { AbilityStage, appRecovery } from '@kit.AbilityKit';
export default class MyAbilityStage extends AbilityStage {
onCreate() {
console.info("[Demo] MyAbilityStage onCreate");
appRecovery.enableAppRecovery(appRecovery.RestartFlag.ALWAYS_RESTART,
appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR | appRecovery.SaveOccasionFlag.SAVE_WHEN_BACKGROUND,
appRecovery.SaveModeFlag.SAVE_WITH_FILE);
}
}
配置支持恢复的UIAbility
UIAbility的配置清单一般的名字为module.json5。
{
"abilities": [
{
"name": "EntryAbility",
"recoverable": true,
}]
}
数据保存和恢复
在使能appRecovery功能后,开发者可以在UIAbility中采用主动保存状态,主动恢复或者选择被动恢复的方式使用appRecovery功能。
下面为示例的EntryAbility。
导包
import { AbilityConstant, appRecovery, errorManager } from '@kit.AbilityKit';
主动触发保存和恢复
- 定义和注册ErrorObserver callback,具体可参考errorManager里的使用方法。
- 数据保存
-
callback触发appRecovery.saveAppState()调用后,会触发EntryAbility的onSaveState(state, wantParams)函数回调。
-
- 数据恢复
-
callback触发后appRecovery.restartApp()调用后,应用会重启,重启后会走到EntryAbility的onCreate(want, launchParam)函数,保存的数据会在want参数的parameters里。
-
- 取消注册ErrorObserver callback
被动保存和恢复
-
被动保存和恢复依赖恢复框架底层触发,无需注册监听ErrorObserver callback,只需实现UIAbility的onSaveState接口数据保存和onCreate接口数据恢复流程即可。
故障UIAbility的重启恢复标记
发生故障的UIAbility再次重新启动时,在调用onCreate生命周期里,参数want的parameters成员会有ABILITY_RECOVERY_RESTART标记数据,并且值为true。
故障分析
分析JS Crash(进程崩溃)
简介
在ArkTS应用中,Crash(崩溃)检测是一项重要的监控能力,它可以帮助开发者及时发现和修复应用中的问题。
检测原理
方舟运行时捕获进程异常。生成故障日志的流程如下:
-
当代码执行时,未捕获的异常或错误导致应用崩溃,方舟运行时将捕获这些异常。
-
方舟运行时收集故障信息,并将其上报给维测进程Hiview。
-
维测进程Hiview补充仅其有权限获取的信息(如整机内存状态、应用页面切换轨迹),生成对应的崩溃日志文件, 存储在“/data/log/faultlog/faultlogger”目录下。
-
上报崩溃事件,开发者可通过HiAppEvent订阅崩溃事件。如需了解JS Crash问题分析方法,请参见JS Crash类问题分析方法。
约束与限制
在async修饰的异步函数中主动抛出异常,不会产生JS Crash导致应用崩溃,开发者可以通过ErrorManager观测该异常,样例代码参考Async函数内部异常的处理机制。
日志获取
进程崩溃日志是一种故障日志,由FaultLogger模块进行管理,可通过以下方式获取:
方式一:通过DevEco Studio获取日志
DevEco Studio会收集设备/data/log/faultlog/faultlogger/路径下的进程崩溃故障日志到FaultLog中,根据进程名、故障和时间分类显示。获取日志的方法参见:DevEco Studio使用指南-FaultLog。
方式二:通过HiAppEvent接口订阅
HiAppEvent给开发者提供了故障订阅接口,详见HiAppEvent介绍。参考订阅崩溃事件(ArkTS)或订阅崩溃事件(C/C++)完成崩溃事件订阅,再通过事件的external_log字段读取故障日志文件内容。
方式三:通过hdc获取日志,需打开开发者选项
在开发者选项打开的情况下,开发者可以通过如下命令获取日志至本地。
hdc file recv /data/log/faultlog/faultlogger 本地路径
故障日志文件名格式为:jscrash-进程名-进程UID-毫秒级时间.log。
日志规格
| 字段 | 描述 | 起始API版本 | 是否必选项 | 非必选说明 |
|---|---|---|---|---|
| Device info | 设备信息 | 8 | 是 | - |
| Build info | 版本信息 | 8 | 是 | - |
| Fingerprint | 故障特征,聚类同类问题的哈希值 | 8 | 是 | - |
| Timestamp | 时间戳 | 8 | 是 | - |
| Module name | 包名/进程名 | 8 | 是 | - |
| Version | hap版本 | 8 | 是 | - |
| Version Code | 版本编码 | 8 | 是 | - |
| Pid | 故障进程号 | 8 | 是 | - |
| Uid | 用户ID | 8 | 是 | - |
| Process life time | 故障进程存活时间 | 22 | 是 | - |
| Process Memory(kB) | 进程占用内存 | 20 | 是 | - |
| Device Memory(kB) | 整机内存信息 | 20 | 否 | 依赖维测服务进程,若发生故障时维测服务进程停止或设备重启则无此字段,详见检测原理。 |
| Page switch history | 页面切换轨迹 | 20 | 否 | 如果维测服务进程出现故障或未缓存切换轨迹,则不包含此字段。 |
| Reason | 故障原因 | 8 | 是 | - |
| Error name | 故障类型 | 8 | 是 | - |
| Error message | 异常信息 | 8 | 是 | - |
| Stacktrace | 故障堆栈 | 8 | 是 | - |
| HybridStack | CPP和JS之间跨语言的故障堆栈 | 22 | 否 | ARM 64位系统下,若Stacktrace为JS栈时,则包含此字段,至多显示256层。 |
| SubmitterStacktrace | 提交者线程栈 | 20 | 否 |
异步线程栈跟踪维测功能默认仅在ARM 64位系统中开启。 对于API version 22之前版本,三方和系统应用libuv和ffrt提交异步任务仅debug版本默认开启。 对于API version 22及之后版本,三方应用通过libuv提交异步任务debug和release版本均默认开启;三方和系统应用通过ffrt提交异步任务仅debug版本默认开启。 |
| HiLog | 故障之前打印的流水日志,最多1000行 | 20 | 是 | - |
以下是JS Crash崩溃日志规格。
Device info:XXX <- 设备信息
Build info:XXX-XXXX X.X.X.XX(XXXXXXXX) <- 版本信息
Fingerprint:ed1811f3f5ae13c7262b51aab73ddd01df95b2c64466a204e0d70e6461cf1697 <- 故障特征
Timestamp:XXXX-XX-XX XX:XX:XX.XXX <- 时间戳
Module name:com.example.myapplication <- 包名/进程名
Version:1.0.0 <- hap版本
VersionCode:1000000 <- 版本编码
Pid:579 <- 故障进程号
Uid:0 <- 用户ID
Process life time:1s <- 进程存活时间
Process Memory(kB): 1897(Rss) <- 进程占用内存
Device Memory(kB): Total 1935820, Free 482136, Available 1204216 <- 整机内存信息
Page switch history: <- 页面切换轨迹
14:08:30:327 /ets/pages/Index:JsError
14:08:28:986 /ets/pages/Index
14:08:07:606 :leaves foreground
14:08:06:246 /ets/pages/Index:AppFreeze
14:08:01:955 :enters foreground
Reason:TypeError <- 故障原因
Error name:TypeError <- 故障类型
Error message:Cannot read property c of undefined <- 异常信息
Cannot get SourceMap info, dump raw stack: <- 应用安装包为release包安装时不包含sourcemap文件,JS栈通过sourcemap行列号解析会失败
Stacktrace:
at onPageShow entry (entry/src/main/ets/pages/Index.ets:7:13) <-异常代码调用堆栈
^ ^ ^
函数名 模块的包名 文件行列号位置
HybridStack: <- CPP和JS之间跨语言的代码调用栈
#00 pc 00000000004a814c /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#01 pc 00000000004a6460 /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#02 pc 00000000006a94e0 /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#03 pc 0000000000334d38 /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#04 pc 0000000000253da8 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::ObjectFactory::GetJSError(panda::ecmascript::base::ErrorType const&, char const*, panda::ecmascript::StackCheck)+292)(173710293c3751dc676d24264bfac393)
#05 pc 00000000005c25d4 /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#06 pc 0000000000de3efc /system/lib64/module/arkcompiler/stub.an(RTStub_PushCallArgsAndDispatchNative+44)
#07 pc 000000000044843c /system/lib64/module/arkcompiler/stub.an(BCStub_HandleCallarg1Imm8V8StwCopy+340)
#08 at onPageShow entry (entry/src/main/ets/pages/Index.ets:7:13) <- 异常发生时执行的JS代码
#09 pc 00000000001e620c /system/lib64/platformsdk/libark_jsruntime.so(173710293c3751dc676d24264bfac393)
#10 pc 00000000009ad560 /system/lib64/platformsdk/libark_jsruntime.so(panda::FunctionRef::Call(panda::ecmascript::EcmaVM const*, panda::Local<panda::JSValueRef>, panda::Local<panda::JSValueRef> const*, int)+456)(173710293c3751dc676d24264bfac393)
#11 pc 0000000000a63f14 /system/lib64/platformsdk/libace_compatible.z.so(e236e26a38ac303814f43a3c8fc9b0a6)
#12 pc 0000000000d836bc /system/lib64/platformsdk/libace_compatible.z.so(e236e26a38ac303814f43a3c8fc9b0a6)
#13 pc 000000000111f338 /system/lib64/platformsdk/libace_compatible.z.so(e236e26a38ac303814f43a3c8fc9b0a6)
...
HiLog:
^
在生成的崩溃日志文件中追加产生故障之前的流水日志,最多1000行
异步线程栈跟踪故障场景日志规格
当异步线程发生崩溃后,把提交该异步任务的线程栈也打印出来,帮助定位由于异步任务提交者造成的崩溃问题。崩溃线程的调用栈和其提交线程的调用栈通过SubmitterStacktrace字符串分隔。以下是一份DevEco Studio归档在FaultLog的进程崩溃日志的核心内容。
注意
异步线程栈跟踪维测功能默认仅在ARM 64位系统中开启,在JSCrash崩溃打印Native堆栈时适用。
对于API version 22之前版本,三方和系统应用通过libuv和ffrt提交异步任务仅debug版本默认开启。
对于API version 22及之后版本,三方应用通过libuv提交异步任务debug和release版本均默认开启,三方和系统应用通过ffrt提交异步任务仅debug版本默认开启。
Page switch history页面切换轨迹
从API 20起,使用Page switch history段记录页面切换的历史,故障日志最多记录最新的10条历史轨迹。单条记录的格式如下:
14:08:30:327 /ets/pages/Index:JsError
^ ^ ^
切换时间 页面URL 页面名
注意
仅在通过Navigation跳转到子页面时才会有页面名,页面名在系统路由表中定义。
当应用发生前后台切换时,对应的页面URL为空,但是会将enters foreground、leaves foreground作为特殊的页面名进行填充。
enters foreground:应用进入前台运行。
leaves foreground:应用在后台运行。
Reason原因
JS Crash异常根据不同的异常场景,在Reason字段进行了分类,分为Error、TypeError、SyntaxError、RangeError等错误类型。
-
Error(自定义)类:Error是最基本的错误类型,其他的错误类型都继承自该类型。Error对象有两个重要属性:Error message(异常信息)和Error name(错误类型)。程序运行过程中抛出的异常一般都有具体的类型,Error类型一般都是开发人员自己抛出的异常。
-
TypeError(类型错误)类:这是运行时最常见的异常,表示变量或参数不是预期类型。
-
SyntaxError(语法错误)类:语法错误也称为解析错误,表示不符合编程语言的语法规范。
-
RangeError(边界错误)类:表示超出有效范围时发生的异常,具体包括以下几种情况:
- 数组长度为负数或超过最大允许长度。
- 数字类型的方法参数超出预定义的有效范围。
- 函数堆栈调用深度超过最大限制。
-
ReferenceError (引用错误)类:引用一个不存在的变量时发生的错误。创建变量时,变量名称都会被写入变量存储中心。变量存储中心类似于键值存储,引用变量时,会先在存储中心查找对应的键并返回值。如果未找到对应变量,就会抛出ReferenceError。
-
URI Error( URI错误)类:通常发生在处理 URL、URN 或其他资源标识符时,格式不正确或操作非法。主要包括encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这几个函数。
-
OOMError(堆内存不够)类:表示应用程序已经耗尽了可用的堆内存,当无法为新对象分配更多堆内存时,就会抛出此错误。
-
TerminationError(终止错误)类:通常是因为进程被强制终止了。例如,如果Taskpool线程中存在长时间执行或者死循环的任务,将会导致进程被强制终止并抛出此错误。
-
AggregateError(多个错误)类:用于表示多个错误的集合。它通常在需要处理或报告多个错误(而不是单个错误)的场景中使用。
-
EvalError(eval函数错误)类:用于表示与 eval() 函数执行相关的异常。然而,在实际开发中,这个错误类型已经很少被使用,更多情况下引擎会直接抛出 SyntaxError 或 TypeError。
JS Crash通常是应用问题,开发者可通过崩溃文件中的Error message和StackTrace来定位问题。
异常代码调用栈格式
异常代码调用栈内容在Release和Debug两种应用构建模式下存在差异:Debug构建模式会保留完整调试信息,Release构建模式会通过代码优化和混淆技术剥离调试信息。
Release模式
Release模式构建的应用中,异常堆栈信息遵循以下标准化格式:
at <执行方法名> (<本模块名|依赖的模块名|版本号|编译产物路径>:<行号>:<列号>)
示例如下:
at onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13)
格式解析:
-
at:堆栈调用链的固定起始标识符。
-
执行方法名:onPageShow表示触发该异常的调用方法名称。
-
编译产物结构如下:
-
编译产物路径:详见异常堆栈解析原理 sourcemap结构:key字段介绍。
-
文件类型:文件扩展名为.ts文件后缀(.js文件无需 SourceMap 映射可直接定位异常)。
-
-
行列号:发生异常的具体行数和这一行的列数,以“:”为分隔符分隔。
Debug模式
使用 Source Map 转译 Release 模式构建应用的异常堆栈或使用Debug模式构建的应用中,异常堆栈信息遵循以下标准化格式:
at <执行方法名> <源码依赖的模块名> (<源码路径>:<行号>:<列号>)
示例如下:
at onPageShow har1 (har1/src/main/ets/pages/Index.ets:7:13)
格式解析:
-
at:堆栈调用链的固定起始标识符。
-
执行方法名:onPageShow表示触发该异常的调用方法名称。
-
源码依赖的模块名:har1表示源码路径所属模块名。
-
源码路径结构如下:
- 源码路径:基于工程目录的源码文件路径。
- 文件类型:文件扩展名为.ets。
-
行列号:发生异常的具体行数和这一行的列数,以“:”为分隔符分隔。
HybridStack(CPP和JS之间跨语言的故障堆栈)格式
从API 22起,在ARM 64位系统下,HybridStack中支持打印CPP和JS之间跨语言的代码调用栈。
CPP代码调用栈详细说明CPP异常代码调用栈格式规范。
JS代码调用栈详细说明JS异常代码调用栈格式规范。
分析CppCrash(进程崩溃)
简介
进程发生崩溃后,系统首先感知到崩溃,然后抓取崩溃相关的信息,最后生成崩溃日志并上报崩溃事件,为开发者提供详细的维测日志以辅助故障定位。本文分为基本概念、实现原理、约束与限制、日志获取、日志规格五个小节介绍系统提供的CppCrash检测方法。开发者如果想进一步了解如何分析CppCrash问题,请参见CppCrash类问题分析方法。
基本概念
-
信号
信号是兼容POSIX的操作系统中进程间通讯的一种方式。
-
信号处理函数
定义了进程在接收到信号之后进行一系列处理操作的函数,信号处理函数需要明确处理哪些信号。
-
pc
全称Program Counter(程序计数器),储存当前程序正在执行指令的地址。
-
lr
全称Link Register(链接寄存器),存储子程序的返回地址。
-
sp
全称Stack Pointer(堆栈指针寄存器),存储当前函数栈帧的栈顶地址。
-
fp
全称Frame Pointer(栈帧指针寄存器),存储当前函数栈帧的栈底地址。
-
调用栈
记录每个线程从开始到执行当前现场(如崩溃现场)整个过程中函数调用顺序。
-
寄存器
CPU中高速存储单元用于存储计算机程序执行过程中所需的数据、指令地址或状态信息。本文中,寄存器信息是指崩溃时保存在高速存储单元中的数据、指令地址或状态信息。
实现原理
系统检测进程崩溃的流程如下:
-
进程在运行时发生崩溃,会收到来自内核发送的崩溃信号,由进程在启动时注册的信号处理模块进行处理。
-
进程接收到崩溃信号后,保存当前进程上下文,并fork出子进程执行ProcessDump二进制抓取崩溃信息。
-
ProcessDump进程将崩溃日志数据写入到临时目录下进行存储。
-
ProcessDump进程收集完崩溃日志后,上报给维测进程Hiview,并补充仅Hiview有权限获取的部分信息(如整机内存状态、应用页面切换轨迹),然后将崩溃日志存储到“/data/log/faultlog/faultlogger”目录下并生成故障事件。
系统处理的崩溃信号
系统的进程崩溃检测能力主要基于POSIX信号机制,目前支持对以下崩溃信号的处理:
| 信号值(signo) | 信号 | 解释 | 触发原因 |
|---|---|---|---|
| 4 | SIGILL | 非法指令 | 进程执行了非法、格式错误、未知或特权指令。 |
| 5 | SIGTRAP | 断点或陷阱异常 | 异常或trap指令发生。 |
| 6 | SIGABRT | 进程终止 | 进程异常终止,通常为进程自身调用标准函数库的abort()函数。 |
| 7 | SIGBUS | 非法内存访问 | 进程访问了未对齐或者不存在的物理地址。 |
| 8 | SIGFPE | 浮点异常 | 进程执行了错误的算术运算,如除数为0、浮点溢出、整数溢出等。 |
| 11 | SIGSEGV | 无效内存访问 | 进程访问了无效内存引用。 |
| 16 | SIGSTKFLT | 栈错误 | 处理器执行了错误的栈操作,如栈空时弹出、栈满时压入。 |
| 31 | SIGSYS | 错误系统调用 | 系统调用时使用了错误或非法参数。 |
以上系统处理的崩溃信号,根据错误码(code)还有二级分类,二级分类如下:
SIGILL崩溃类型
SIGILL是一个在Unix和类Unix操作系统中的信号,它表示非法指令异常。SIGILL信号通常由以下几种类型的问题场景引起:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 1 | ILL_ILLOPC | 非法操作码异常 | 发生在执行不被CPU支持的指令或者无效指令时。 |
| 2 | ILL_ILLOPN | 非法操作数异常 | 发生在指令使用了不正确的操作数,或者是操作数的类型不正确时。 |
| 3 | ILL_ILLADR | 非法地址异常 | 发生在程序尝试访问无效的内存地址时,或者是在尝试执行未对齐的内存访问时。 |
| 4 | ILL_ILLTRP | 非法陷阱异常 | 发生在程序尝试执行一个非法的陷阱指令时,或者是在尝试执行一个未定义的操作时。 |
| 5 | ILL_PRVOPC | 特权操作码异常 | 发生在普通用户尝试执行特权指令时。 |
| 6 | ILL_PRVREG | 特权寄存器异常 | 发生在普通用户尝试访问特权寄存器时。 |
| 7 | ILL_COPROC | 协处理器异常 | 发生在程序尝试使用未定义的协处理器指令时。 |
| 8 | ILL_BADSTK | 无效的堆栈异常 | 发生在程序尝试在无效的堆栈地址上执行操作时,或者是在堆栈溢出时。 |
| 0xacac | ILL_ILLPACCFI | 指针校验异常 | 发生在程序校验指针失败时。 |
SIGTRAP崩溃类型
SIGTRAP信号通常用于调试和跟踪程序的执行。下面是SIGTRAP信号类别的问题场景介绍:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 1 | TRAP_BRKPT | 软件断点 | 由软件断点引起的,当程序执行到设置的断点时会触发该信号。软件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的执行并检查变量值等信息。 |
| 2 | TRAP_TRACE | 单步调试 | 由单步执行引起的,当程序执行单个指令时会触发该信号。单步执行通常用于调试程序,可以逐步执行程序并检查每个指令的执行结果。 |
| 3 | TRAP_BRANCH | 分支跟踪 | 由分支指令引起的,当程序执行分支指令时会触发该信号。分支指令通常用于控制程序的执行流程,例如if语句和循环语句等。 |
| 4 | TRAP_HWBKPT | 硬件断点 | 由硬件断点引起的,当程序执行到设置的硬件断点时会触发该信号。硬件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的执行并检查变量值等信息。与软件断点不同的是,硬件断点是由CPU硬件实现的,因此可以在程序执行过程中实时检测断点是否被触发。 |
SIGBUS崩溃类型
SIGBUS是一种由操作系统向进程发送的信号,通常表示内存访问错误。其中,不同的信号类别表示不同的错误场景:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 1 | BUS_ADRALN | 内存地址对齐错误 | 发生在尝试访问未对齐的内存地址时,例如尝试访问一个4字节整数的非偶数地址。 |
| 2 | BUS_ADRERR | 非法内存地址错误 | 发生在尝试访问不属于进程地址空间的内存地址时,例如尝试访问一个空指针。 |
| 3 | BUS_OBJERR | 对象访问错误 | 发生在尝试访问一个已经被删除或未初始化的对象时。 |
| 4 | BUS_MCEERR_AR | 需立即处理的硬件内存校验错误 | 发生在访问内存时检测到需要立即处理的硬件内存错误。 |
| 5 | BUS_MCEERR_AO | 可等待或延迟处理的硬件内存校验错误 | 发生在访问内存时检测到可等待或延迟处理的硬件内存错误。 |
SIGFPE崩溃类型
SIGFPE是一个信号,它表示浮点异常或算术异常。下面是这些SIGFPE信号类别的问题场景:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 1 | FPE_INTDIV | 整数除法错误 | 表示整数除法中的除数为零的情况。当一个程序尝试进行整数除法,但除数为零时,会发出这个信号。 |
| 2 | FPE_INTOVF | 整数溢出错误 | 表示整数溢出错误。当一个程序尝试进行整数运算,结果超出整数范围时,会发出这个信号。 |
| 3 | FPE_FLTDIV | 浮点除法错误 | 表示浮点数除法中的除数为零的情况。当一个程序尝试进行浮点数除法,但除数为零时,会发出这个信号。 |
| 4 | FPE_FLTOVF | 浮点上溢错误 | 表示浮点溢出错误。当一个程序尝试进行浮点数运算,结果超出浮点数上限范围时,会发出这个信号。 |
| 5 | FPE_FLTUND | 浮点下溢错误 | 表示浮点下溢错误。当一个程序尝试进行浮点数运算,结果小于浮点数下限范围时,会发出这个信号。 |
| 6 | FPE_FLTRES | 浮点结果未定义错误 | 表示浮点结果未定义错误。当一个程序尝试进行浮点数运算,结果未定义时,会发出这个信号。 |
| 7 | FPE_FLTINV | 无效浮点操作错误 | 表示无效浮点操作错误。当一个程序尝试进行无效的浮点数运算时,会发出这个信号。 |
| 8 | FPE_FLTSUB | 浮点运算结果越界错误 | 表示浮点运算结果越界错误。当一个程序尝试进行浮点数运算,浮点数结果越界,会发出这个信号。 |
SIGSEGV崩溃类型
SIGSEGV是一种信号,它表示进程试图访问一个不属于它的内存地址,或者试图访问一个已被操作系统标记为不可访问的内存地址。SIGSEGV信号通常是由以下两种情况引起的:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 1 | SEGV_MAPERR | 不存在的内存地址 | 进程试图访问一个不存在的内存地址,或者试图访问一个没有映射到进程地址空间的内存地址。这种情况通常是由于程序中的指针错误或内存泄漏引起的。 |
| 2 | SEGV_ACCERR | 不可访问的内存地址 | 进程试图访问一个已被操作系统标记为不可访问的内存地址,例如向只读内存写入数据或执行没有执行权限的内存。这种情况通常是由于程序中的缓冲区溢出或者试图修改只读内存等错误引起的。 |
信号产生原因分类
除了以上根据信号值维度分类,还可以根据信号产生的原因维度分类。所有信号值都可以按照信号产生的原因分类,当前已有信号产生原因分类的code值如下:
| 错误码(code) | 信号字符串 | 解释 | 触发原因 |
|---|---|---|---|
| 0 | SI_USER | 用户空间信号 | 由用户空间的进程发送给进程的,通常是通过kill()系统调用发送的。例如,当用户在终端中按下Ctrl+C时,会发送一个SIGINT信号给前台进程组中的所有进程。 |
| 0x80 | SI_KERNEL | 内核信号 | 由内核发送给进程的,通常是由内核检测到某些错误或异常情况时发出的。例如,当进程访问无效的内存地址或者执行非法指令时,内核会发送一个SIGSEGV信号给进程。 |
| -1 | SI_QUEUE | sigqueue()函数信号 | 由sigqueue()系统调用发送的,可以携带一个附加的整数值和一个指针。通常用于进程间高级通信,例如传递数据或者通知进程某个事件已经发生。 |
| -2 | SI_TIMER | 定时器信号 | 由定时器发送的,通常用于定时任务或者周期性任务的执行。例如,当一个定时器到期时,内核会向进程发送一个SIGALRM信号。 |
| -3 | SI_MESGQ | 消息队列信号 | 由消息队列发送的,通常用于进程间通信。例如,当一个进程向一个消息队列发送消息时,内核会向接收进程发送一个MESGQ信号。 |
| -4 | SI_ASYNCIO | 异步I/O信号 | 由异步I/O操作发送的,通常用于非阻塞I/O操作。例如,当一个文件描述符上的I/O操作完成时,内核会向进程发送一个ASYNCIO信号。 |
| -5 | SI_SIGIO | 同步I/O信号 | 由同步I/O操作发送的,通常用于阻塞I/O操作。例如,当一个文件描述符上的I/O操作完成时,内核会向进程发送一个SIGIO信号。 |
| -6 | SI_TKILL | tkill()函数信号 | 由tkill()系统调用发送的,与kill()系统调用类似,但是可以指定发送信号的线程ID。通常用于多线程程序中,向指定线程发送信号。 |
约束与限制
-
不建议进程自己注册信号处理函数,进程崩溃后可能会延迟退出,当处理时间超过5s可能会导致进程无响应问题上报。
-
“/data/log/faultlog/faultlogger”目录下同一进程/应用最多保存10份cppcrash日志,日志数量超限时会从最早生成该进程/应用的cppcrash日志开始删除直至数量不超限。建议开发者在开发调试阶段及时查看cppcrash日志,避免因cppcrash日志被删除而无法获取崩溃信息。
-
上述崩溃信号和35、38、42信号已经被系统注册信号处理函数,建议应用不要对这些信号注册信号处理函数,如果应用注册了可能会造成系统检测能力失效。
-
异步线程栈跟踪维测功能默认仅在ARM 64位系统中开启。对于API version 22之前版本,三方和系统应用通过libuv和ffrt提交异步任务仅debug版本默认开启。对于API version 22及之后版本,三方应用通过libuv提交异步任务debug和release版本均默认开启,三方和系统应用通过ffrt提交异步任务仅debug版本默认开启。崩溃日志规格请参见异步线程栈跟踪故障场景日志规格。
日志获取
进程崩溃日志是故障日志中的一种,可通过以下方式获取:
方式一:通过DevEco Studio获取日志
DevEco Studio会收集设备“/data/log/faultlog/faultlogger/”路径下的进程崩溃故障日志到FaultLog下,根据进程名和故障类型分类显示。获取日志的方法参见:DevEco Studio使用指南-FaultLog。
方式二:通过HiAppEvent接口订阅
HiAppEvent给开发者提供了故障订阅接口,详见HiAppEvent介绍。参考订阅崩溃事件(ArkTS)或订阅崩溃事件(C/C++)完成崩溃事件订阅,并通过事件的external_log字段读取故障日志文件内容。
方式三:通过hdc获取日志,需打开开发者选项
在开发者选项打开的情况下,开发者可以通过“hdc file recv /data/log/faultlog/faultlogger D:\”命令导出故障日志到本地,故障日志文件名格式为:cppcrash-进程名-进程UID-毫秒级时间.log。
日志规格
故障日志的字段信息表如下:
| 字段 | 描述 | 起始API版本 | 是否必选项 | 非必选说明 |
|---|---|---|---|---|
| Device info | 设备信息 | 8 | 是 | - |
| Build info | 版本信息 | 8 | 是 | - |
| Fingerprint | 故障特征,聚类同类问题的哈希值 | 8 | 是 | - |
| Enabled app log configs | 使能的配置参数列表 | 20 | 否 | 仅用户配置时打印,详见应用通过HiAppEvent设置崩溃日志配置参数场景日志规格。 |
| Module name | 模块名 | 8 | 是 | - |
| Version | 应用版本号(点分格式) | 8 | 否 | 仅在应用进程提供。 |
| VersionCode | 应用版本号(整数格式) | 8 | 否 | 仅在应用进程提供。 |
| PreInstalled | 是否预制应用 | 8 | 否 | 仅在应用进程提供。 |
| Foreground | 前后台状态 | 8 | 否 | 仅在应用进程提供。 |
| Page switch history | 页面切换轨迹 | 20 | 否 | 如果维测服务进程出现故障或未缓存切换轨迹,则不包含此字段,详见实现原理。 |
| Timestamp | 故障发生时间戳 | 8 | 是 | - |
| Pid | 进程号 | 8 | 是 | - |
| Uid | 用户ID | 8 | 是 | - |
| HiTraceId | HiTraceChain唯一跟踪标识 | 20 | 否 | 仅故障线程开启HiTraceChain功能时提供,详见HiTraceChain介绍。 |
| Process name | 故障进程名 | 8 | 是 | - |
| Process life time | 故障进程存活时间 | 8 | 是 | - |
| Process Memory(kB) | 故障进程内存占用 | 20 | 是 | - |
| Device Memory(kB) | 整机内存状态 | 20 | 否 | 依赖维测服务进程,若发生故障时维测服务进程停止或设备重启则无此字段,详见实现原理。 |
| Reason | 故障原因 | 8 | 是 | - |
| LastFatalMessage | 应用记录的最后一条Fatal级日志 | 8 | 否 | 进程主动abort,hilog中打印包含最后一条Fatal日志时。 |
| Fault thread info | 故障线程信息 | 8 | 是 | - |
| SubmitterStacktrace | 提交者线程栈 | 12 | 否 |
异步线程栈跟踪维测功能默认仅在ARM 64位系统中开启。 对于API version 22之前版本,三方和系统应用通过libuv和ffrt提交异步任务仅debug版本默认开启。 对于API version 22及之后版本,三方应用通过libuv提交异步任务debug和release版本均默认开启;三方和系统应用通过ffrt提交异步任务仅debug版本默认开启。 |
| Registers | 故障现场寄存器 | 8 | 是 | - |
| Other thread info | 其他线程信息 | 8 | 是 | - |
| Memory near registers | 故障现场寄存器附近内存值 | 8 | 是 | - |
| FaultStack | 故障线程栈内存信息 | 8 | 是 | - |
| Maps | 故障时进程的内存空间 | 8 | 是 | - |
| OpenFiles | 故障时进程持有的文件句柄信息 | 12 | 是 | - |
| HiLog | 故障之前打印的流水日志,最多1000行 | 8 | 是 | - |
| [truncated] | 故障日志截断标志 | 20 | 否 | 配置故障日志截断大小并发生截断时。 |
说明
从API version 20开始,故障现场寄存器中新增状态寄存器pstate和esr。
不同的故障场景中日志规格略有不同,分以下七个场景的日志规格,示例如下:
说明
以下崩溃日志示例中"<-"右边的文字不是日志内容,是用来解释日志格式的说明文字。
一般故障场景日志规格
本节主要介绍崩溃日志的一般日志规格,其余节介绍特殊场景下日志规格,以下是一份DevEco Studio归档在FaultLog的进程崩溃日志的核心内容。各种场景下的日志规格说明具体参考官方课程
,不再整理。
分析AppFreeze(应用无响应)
简介
用户在使用应用时,如果出现点击无反应或应用无响应等情况,并且持续时间超过一定限制,就会被定义为应用冻屏(AppFreeze),即应用无响应。系统会检测应用无响应,并生成AppFreeze日志,供应用开发者分析。
说明
本文仅适用于Stage模型下的应用。在根据本文分析日志前,开发者需要具备JS在系统中运行情况、C++程序堆栈信息的相关基础知识,并对应用相关的子系统有一定了解。
检测原理
目前应用冻屏检测从以下维度检测,了解其原理有助于应用开发者定位和分析AppFreeze故障。
注意
AppFreeze检测仅对release版本应用生效,对debug版本应用不生效。
| 故障类型 | 说明 |
|---|---|
| THREAD_BLOCK_6S | 应用主线程卡死超时 |
| APP_INPUT_BLOCK | 用户输入响应超时 |
当应用发生上述故障时,为了保证可恢复,会杀死应用。并上报应用冻屏事件,可通过HiAppEvent订阅应用冻屏事件。
THREAD_BLOCK_6S 应用主线程卡死超时
概述:发生该故障,表示当前应用主线程有卡死或者执行任务过多的情况,影响应用的流畅度和体验。
检测原理:应用的watchdog线程定期向主线程插入判活检测任务。如果判活检测超过3s未被执行,将上报THREAD_BLOCK_3S告警事件;如果超过6s仍未被执行,将上报THREAD_BLOCK_6S主线程卡死事件。两个事件匹配生成THREAD_BLOCK的应用无响应日志。
检测原理如下图:

APP_INPUT_BLOCK 用户输入响应超时
概述:该故障是指点击事件超过5s未得到响应。
检测原理:用户点击应用时,输入系统会向应用侧发送点击事件;应用侧的响应反馈回执超时,则上报该故障。
检测原理如下图:

日志获取
应用冻屏日志和进程崩溃日志一致,都是由Faultlogger模块进行管理,可通过以下方式获取:
方式一:通过DevEco Studio获取日志
DevEco Studio会收集设备/data/log/faultlog/faultlogger/路径下的进程崩溃故障日志到FaultLog下,根据进程名和故障时间分类显示。获取日志的方法参见:DevEco Studio使用指南-FaultLog。
方式二:通过HiAppEvent接口订阅
HiAppEvent给开发者提供了故障订阅接口,详见HiAppEvent介绍。参考订阅应用冻屏事件(ArkTS)或订阅应用冻屏事件(C/C++)完成应用冻屏事件订阅,并通过事件的external_log字段读取故障日志文件内容。
方式三:通过hdc获取日志,需打开开发者选项
在开发者选项打开的情况下,开发者可以通过hdc file recv /data/log/faultlog/faultlogger D:\命令导出故障日志到本地,故障日志文件名格式为appfreeze-进程名-进程UID-毫秒级时间.log。
日志规格
应用冻屏(AppFreeze)问题需要结合应用冻屏日志和hilog流水日志进行分析。
当前提供一个故障分析示例,请开发者根据具体问题具体分析。
应用冻屏日志主要分以下几个模块信息:
日志头部信息
Generated by HiviewDFX@HarmonyOS
================================================================
Device info:HUAWEI Mate 60 Pro
Build info:ALN-AL00 6.0.0.328(C00E1R4P3DEVDUlog)
Fingerprint:e18a33c12e1361173ec9ac1c93f2bd0c2daa88f03c7f76b228cca14bdc6a21b1
Module name:com.samples.freezedebug
Version:1.0.0
VersionCode:1000000
PreInstalled:No
Foreground:Yes
Pid:13680
Uid:20020177
Process life time:18s
Process Memory(kB):163819(Rss)
Device Memory(kB):Total 11679272, Free 3697424, Available 5814272
Reason:THREAD_BLOCK_6S
appfreeze: com.samples.freezedebug THREAD_BLOCK_6S at 20250628140837
DisplayPowerInfo:powerState:UNKNOWN
HitraceIdInfo: hitrace_id: a92ab27238f409a, span_id: 1cd61c9, parent_span_id: 3072e, trace_flag: 0
Page switch history:
14:08:30:327 /ets/pages/Index:Appfreeze
14:08:28:986 /ets/pages/Index
14:08:26:502 :enters foreground
14:08:07:606 :leaves foreground
14:08:06:246 /ets/pages/Index:Appfreeze
14:08:01:955 :enters foreground
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DOMAIN:AAFWK
STRINGID:THREAD_BLOCK_6S
TIMESTAMP:2025/06/28-14:08:37:360
PID:13680
UID:20020177
PACKAGE_NAME:com.samples.freezedebug
PROCESS_NAME:com.samples.freezedebug
NOTE: Current fault may be caused by the system's low memory or thermal throttling, you may ignore it and analysis other faults.
***
从API version 20开始,当整机资源告警(如整机低内存或热限频)时,会输出NOTE行。出现此行时,开发者可以忽略应用冻屏故障。在之前的API版本中,无论整机资源状态如何,均无此行输出。
从API version 20开始,发生THREAD_BLOCK_6S故障时,日志中新增HiTraceId信息打印。HitraceId是HiTraceChain提供的唯一跟踪标识,用于跟踪业务流程调用链。可以协助开发者查看故障时间段内,故障流程的hilog日志,分析日志查看应用的执行状态。
AppFreeze事件(THREAD_BLOCK_6S、 APP_INPUT_BLOCK)都包含以下几部分信息,具体解释如下:
| 字段 | 说明 |
|---|---|
| Reason | 应用无响应原因,与应用无响应检测能力点对应。 |
| PID | 发生故障时的pid。 |
| PACKAGE_NAME | 应用进程包名。 |
| Page switch history | 从API 20开始,维测进程会记录应用切换历史。应用发生故障后,生成的故障文件将包含页面切换历史轨迹。如果维测服务进程出现故障或未缓存切换轨迹,则不包含此字段。 |
| Process life time |
故障进程存活时间。单位:s。 说明:从API 22开始支持。 |
| Process Memory(kB) |
故障进程内存占用。 说明:从API 22开始支持。 |
| Device Memory(kB) |
整机内存状态。 说明:从API 22开始支持。 |
日志主干通用信息
start time: 2025/06/28-14:08:34:318
DOMAIN = AAFWK
EVENTNAME = THREAD_BLOCK_3S
TIMESTAMP = 2025/06/28-14:08:34:310
PID = 13680
UID = 20020177
TID = 13680
PACKAGE_NAME = com.samples.freezedebug
PROCESS_NAME = com.samples.freezedebug
eventLog_action = ffrt,t,GpuStack,cmd:m,hot
eventLog_interval = 10
MSG =
Fault time:2025/06/28-14:08:34
App main thread is not response!
Main handler dump start time: 2025-06-28 14:08:34.067
mainHandler dump is:
EventHandler dump begin curTime: 2025-06-28 14:08:34.067
Event runner (Thread name = , Thread ID = 13680) is running
Current Running: start at 2025-06-28 14:08:27.354, Event { send thread = 13680, send time = 2025-06-28 14:08:22.353, handle time = 2025-06-28 14:08:27.353, trigger time = 2025-06-28 14:08:27.354, task name = uv_timer_task, caller = [ohos_loop_handler.cpp(OnTriggered:72)] }
History event queue information:
No. 0 : Event { send thread = 13856, send time = 2025-06-28 14:08:22.336, handle time = 2025-06-28 14:08:22.336, trigger time = 2025-06-28 14:08:22.336, completeTime time = 2025-06-28 14:08:22.337, priority = VIP, task name = MMITask, caller = [deamon_io_waiter.cpp(HandleFileDescriptorEvent:225)] }
...
No. 31 : Event { send thread = 13856, send time = 2025-06-28 14:08:22.330, handle time = 2025-06-28 14:08:22.380, trigger time = 2025-06-28 14:08:22.330, completeTime time = 2025-06-28 14:08:22.331, priority = VIP, task name = vSyncTask, caller = [deamon_io_waiter.cpp(PostTaskForVsync:159)] }
VIP priority event queue information:
No.1 : Event { send thread = 13843, send time = 2025-06-28 14:08:31.066, handle time = 2025-06-28 14:08:31.066, id = 1, caller = [watchdog.cpp(Timer:233)] }
No.2 : Event { send thread = 13843, send time = 2025-06-28 14:08:34.067, handle time = 2025-06-28 14:08:34.067, id = 1, caller = [watchdog.cpp(Timer:233)] }
Total size of VIP events : 2
Immediate priority event queue information:
Total size of Immediate events : 0
High priority event queue information:
Total size of High events : 0
Low priority event queue information:
Total size of Low events : 0
Idle priority event queue information:
Total size of Idle events : 0
Total event size : 2
AppFreeze事件(THREAD_BLOCK_6S、 APP_INPUT_BLOCK)都包含以下几部分信息,具体解释如下:
| 主要信息字段 | 说明 |
|---|---|
| EVENTNAME | 组成卡死检测事件。 |
| TIMESTAMP | 发生故障时上报事件的时刻,可以根据应用无响应检测能力点中说明的超时时间,在相应hilog流水日志中缩小查看日志的时间范围。 |
| PID | 发生故障时候的pid。 |
| UID | 发生故障时候的uid。 |
| TID | 发生故障时候的tid。 |
| PACKAGE_NAME | 应用进程包名。 |
| PROCESS_NAME | 应用进程名。 |
| MSG | 发生故障时间及EventHandler信息。 |
EventHandler信息,具体解释如下:
dump信息组成结构:
| 主要信息字段 | 说明 |
|---|---|
| EventHandler dump begin curTime | 获取dump信息时间。 |
| Event runner | EventHandler对应的线程名和线程号。 |
| Current Running | 当前正在执行的完整任务信息。 |
| History event queue information | 历史执行任务信息。 |
| VIP priority event queue information | VIP级任务队列信息。 |
| Immediate priority event queue information | 立即执行任务队列信息。 |
| High priority event queue information | 高优先级任务队列信息。 |
| Low priority event queue information | 低优先级任务队列信息。 |
| Idle priority event queue information | 挂起任务队列信息。 |
任务组成部分:
| 主要信息字段 | 说明 |
|---|---|
| send thread | 提交任务线程号。 |
| send time | 提交任务时间。 |
| task name | 任务队列中的任务名。 |
| priority | 任务优先级。 |
| caller | 任务提交方法。 |
| handle time | 任务预期执行时间。与实际的任务执行时间(trigger time)可能存在偏差 |
| trigger time | 任务执行时间。 |
| completeTime time | 任务执行完成时间(如果没有打印则表示该任务未执行完成)。 |
说明
EventHandler信息中,开发者只需重点关注EventHandler dump begin curTime,trigger time和completeTime time时间即可。
EventHandler信息详细指导可参考:查看eventHandler信息
堆栈信息
大部分情况下,THREAD_BLOCK_6S、APP_INPUT_BLOCK故障的堆栈信息,可以协助开发者定位到异常代码。
其他情况下(比如瞬时栈场景),由于主线程繁忙等问题,导致获取堆栈信息延迟,无法及时捕获到异常代码段,堆栈的栈顶信息并非开发者期望获取的结果。
为解决上述问题,从API version 21开始,支持获取AppFreeze的增强日志。协助开发者定位问题,详见AppFreeze(应用冻屏)增强日志实现原理。
说明
在整机高负载情况(如CPU高负载)下,采用低开销方式获取调用栈时,可能损失函数名信息。
从API version 21开始,出现'Failed to dump normal stacktrace'字样时,系统采取轻量化的frame pointer回溯模式。栈回溯可能中断在非使能frame pointer的库(在GCC编译使用 -fomit-frame-pointer 选项时,编译产物不使能frame pointer),以及受轻量化的限制,单个线程的回栈层数不会超过50层。
对端信息(与当前故障进程通信的进程信息)
(1)BinderCatcher:显示进程间通信的调用信息及等待时间过长的情况。
PeerBinderCatcher -- pid==13680
BinderCatcher --
13840:14102 to 901:4079 code 16 wait:0.25653125 s frz_state:3, ns:-1:-1 to -1:-1, debug:13840:14102 to 901:4079, active_code:0, active_thread=0, pending_async_proc=0
3712:3712 to 13967:14076 code d2 wait:0.703385417 s frz_state:3, ns:-1:-1 to -1:-1, debug:3712:3712 to 13967:14076, active_code:0, active_thread=0, pending_async_proc=0
1733:2285 to 3712:3712 code b wait:1.365925521 s frz_state:3, ns:-1:-1 to -1:-1, debug:1733:2285 to 3712:3712, active_code:0, active_thread=0, pending_async_proc=0
...
pid context request started max ready free_async_space
14072 binder 0 2 16 3 520192
14103 binder 0 4 16 6 520192
13967 binder 0 3 16 3 520192
13878 binder 0 2 16 3 520192
13840 binder 0 2 16 3 520192
13863 binder 0 1 16 3 520192
13680 binder 0 2 16 3 520192
13770 binder 0 3 16 5 520192
13749 binder 0 3 16 5 520192
...
进程间通信调用信息解释如下:
| 主要信息字段 | 说明 |
|---|---|
| xxx:xxx to xxx:xxx | 客户端进程号、线程号 to 服务端进程号、线程号。其中async表示异步,无async表示同步。 |
| code | 客户端和服务端达成一致约束的业务码。 |
| wait | 通信等待时长。 |
| frz_state |
进程冻结状态。 -1 未知; 1 默认; 2 正在向用户态发送binder状态信息; 3 走到了binder接收线程。 |
| ns | 客户端进程号、线程号 to 服务端进程号、线程号(非卓易通内进程显示-1)。 |
| debug | IPC通信双方的信息补充。 |
| active_code | 正在处理的异步消息code。 |
| active_thread | 处理这个异步消息的线程。 |
| pending_async_proc | 被这个异步消息阻塞的进程。 |
| pid | 进程PID。 |
| context | 通信方式。 |
| request | 请求申请的IPC线程数量。 |
| started | 已经申请启动的IPC线程数量。 |
| max | 可以申请的最大IPC线程数量。 |
| ready | 空闲IPC线程。 |
| free_async_space | 空闲异步空间,用于观测异步信息阻塞。 |
(2)PeerBinder Stacktrace:故障进程相关的对端进程有卡死,会抓取对端的进程堆栈,即与当前进程通信的进程堆栈
Tid:48841, Name:xxx
#00 pc 000000000016adf4 /system/lib/ld-musl-aarch64.so.1
#01 pc 000000000001c0d4 /system/lib64/chipset-sdk-sp/libeventhandler.z.so
#02 pc 000000000001a7f4 /system/lib64/chipset-sdk-sp/libeventhandler.z.so
#03 pc 000000000003f5f4 /system/lib64/chipset-sdk-sp/libeventhandler.z.so
#04 pc 00000000000a50f0 /system/lib64/platformsdk/libappkit_native.z.so
#05 pc 0000000000005278 /system/lib64/appspawn/appspawn/libappspawn_ace.z.so
#06 pc 000000000000baa8 /system/bin/appspawn
#07 pc 0000000000016478 /system/bin/appspawn
#08 pc 0000000000013aac /system/bin/appspawn
#09 pc 0000000000017844 /system/lib64/chipset-sdk-sp/libbegetutil.z.so
#10 pc 000000000001715c /system/lib64/chipset-sdk-sp/libbegetutil.z.so
#11 pc 000000000001444c /system/lib64/chipset-sdk-sp/libbegetutil.z.so
#12 pc 0000000000013edc /system/lib64/chipset-sdk-sp/libbegetutil.z.so
#13 pc 0000000000010dcc /system/bin/appspawn
#14 pc 000000000000eb90 /system/bin/appspawn
#15 pc 00000000000a9804 /system/lib/ld-musl-aarch64.so.1
#16 pc 000000000000b754 /system/bin/appspawn
CPU信息
整机CPU信息及解释如下:
Load average: 14.3 / 12.9 / 11.4; the cpu load average in 1 min, 5 min and 15 min
CPU usage from 2025-06-28 14:08:36 to 2025-06-28 14:08:37
Total: 22.45%; User Space: 13.64%; Kernel Space: 8.81%; iowait: 0.33%; irq: 0.07%; idle: 77.15%
Details of Processes:
PID Total Usage User Space Kernel Space Page Fault Minor Page Fault Major Name
13680 8.86% 8.31% 0.55% 4711 6637 com.samples.freezedebug
644 2.55% 1.40% 1.15% 210104 7391 hiview
600 0.89% 0.78% 0.10% 60192 514 hilogd
1685 0.53% 0.31% 0.22% 879838 59636 foundation
| 主要信息字段 | 说明 |
|---|---|
| PID | 进程PID。 |
| Total Usage | CPU使用率,Total Usage = User Space+Kernel Space。 |
| User Space | 用户态使用率。 |
| Kernel Space | 内核态使用率。 |
| Page Fault Minor | 次要缺页错误。 |
| Page Fault Major | 主要缺页错误。 |
| Name | 进程名。 |
内存信息
Get freeze memory start time: 2025-06-28 14:08:37.112
some avg10=56.81 avg60=56.81 avg300=56.81 total=56
full avg10=56.81 avg60=56.81 avg300=56.81 total=56
...
ReclaimAvailBuffer: 4676608 kB
...
整机内存信息如上所示,其中ReclaimAvailBuffer为整机剩余可用内存,主要用于确认是否是低内存问题。
日志差异性信息
APP_INPUT_BLOCK 用户输入响应超时
Generated by HiviewDFX@HarmonyOS
================================================================
...
Reason:APP_INPUT_BLOCK
appfreeze: com.samples.freezedebug APP_INPUT_BLOCK at 20251129123745
Wait Event(430) to be marked exceed 8000ms, lastDispatchEvent(430), lastProcessEvent(429), lastMarkedEvent(428)
DisplayPowerInfo:powerState:AWAKE
...
从API version 22开始,发生APP_INPUT_BLOCK故障时,日志中会同步输出多模点击输入(包含鼠标、键盘、触控板及触屏等输入方式)超时事件(Wait Event)。该事件信息中包含事件id,事件检测超时阈值,及前置事件id。
事件检测超时阈值:log版本为8000毫秒,nolog版本为5000毫秒。
前置事件中包含:lastDispatchEvent为上次分发的事件;lastProcessEvent为上次处理的事件;lastMarkedEvent为上次标记的事件。
在上述示例中,上次分发的事件是430,上次处理的事件是429,上次标记的事件是428,表明430事件已分发完毕,在处理环节,已超时8000毫秒。此日志可以用于APP_INPUT_BLOCK事件的简单定界,帮助问题分析。
AppFreeze(应用冻屏)增强日志信息
从API version 21开始,支持获取AppFreeze的增强日志。该日志通过采集整机及主线程的运行负载,并抓取多份主线程调用栈,帮助开发者分析问题根源。相比原有日志,AppFreeze的增强日志主要解决了以下两个不足:
-
难以定位故障期间的主线程热点。
-
缺乏对主线程繁忙或阻塞的资源层面分析。
实现原理
生成AppFreeze增强日志的流程分为以下两个阶段,具体如下:
-
应用进程在运行时发生THREAD_BLOCK_3S时,会开启采集主线程调用栈流程,记录当前时刻的一些CPU信息。
-
应用进程在运行时发生THREAD_BLOCK_6S或APP_INPUT_BLOCK时,会停止上述流程的采集主线程调用栈流程,并计算周期内的CPU信息。一般情况下,会抓取1~10次堆栈日志。
说明
由于应用冻屏事件的采样栈会与MAIN_THREAD_JANK冲突,如果应用接入MAIN_THREAD_JANK的setEventConfig接口自定义配置采集堆栈的个数,应用冻屏事件的采集堆栈的会与应用当前配置的采集堆栈的个数一致。
APP_INPUT_BLOCK故障有增强日志的前提是:先发生THREAD_BLOCK_3S。
日志获取
开发者可通过以下方式获取Appfreeze增强日志路径:
方式一:通过HiAppEvent接口订阅
应用需要在AppScope/app.json5文件中配置如下环境变量:
"appEnvironments": [
{
"name": "DFX_APPFREEZE_LOG_OPTIONS",
"value": "mainthread_sampling:enable"
}
]
HiAppEvent给开发者提供了故障订阅接口,开发者可以使用HiAppEvent监听应用冻屏事件,获取文件内容,详见应用冻屏事件介绍。参考订阅应用冻屏事件(ArkTS)或订阅应用冻屏事件(C/C++)完成应用冻屏事件订阅。
开发者可以通过事件的external_log字段读取应用冻屏事件生成的故障文件和应用冻屏事件的增强日志文件。增强日志文件的命名与应用冻屏事件生成的故障文件名称格式相同。
external_log是字符串数组,第一个元素为应用冻屏事件生成的故障文件路径;第二个元素为应用冻屏事件生成的增强日志文件路径。
方式二:通过hdc获取日志,需打开开发者选项
在开发者选项打开的情况下,开发者可以通过“hdc file recv /data/log/faultlog/freeze_ext D:\”命令导出故障日志到本地,故障日志文件名格式为:freeze-cpuinfo-ext-进程名-进程UID-毫秒级时间。
日志规格
增强日志的信息头字段如下:
| 字段 | 描述 | 起始API版本 |
|---|---|---|
| TimeStamp | 日志生成时间。 | 21 |
| Module name | 故障模块名。 | 21 |
增强日志的CPU总体耗时信息字段如下:
| 字段 | 描述 | 起始API版本 |
|---|---|---|
| ProcessCpuTime | 统计周期内,进程运行时间。 | 21 |
| DeviceRuntime | 统计周期内,设备所有CPU的运行时间。 | 21 |
| Tid | 线程号。 | 21 |
| StartTime | 统计开始时间。 | 21 |
| EndTime | 统计结束时间。 | 21 |
| StaticsDuration | 统计持续时间。 | 21 |
| CpuTime | 统计周期内,主线程运行时间。 | 21 |
| SyncWaitTime | 主线程等待时间。 | 21 |
| OptimalCpuTime | 统计周期内,主线程最优负载运行时间(使用最大核的最高算力运行)。 | 21 |
| SupplyAvailableTime | 调度可优化时间。如果值较小,说明主线程繁忙,需要开发者考虑优化主线程的任务。 | 21 |
增强日志的堆栈信息字段如下:
| 字段 | 描述 | 起始API版本 |
|---|---|---|
| SnapshotTime | 本次抓取主线程堆栈的时间。 | 21 |
| ThreadInfos Tid | 线程号。 | 21 |
| Name | 线程名。 | 21 |
| Stack | 主线程调用栈。 | 21 |
| SubmitterStacktrace | 任务提交者调用栈。 | 21 |
增强日志规格
本节主要介绍增强日志的一般日志规格,以下是一份Appfreeze增强日志的核心内容。可以使用聚类脚本提取主线程堆栈关键信息,提高问题定位效率及准确性。
日志举例参考官方课程。
Native栈帧内容说明
详细说明见Cpp Crash一般故障场景日志规格中的调用栈帧内容说明。
JS混合栈帧内容说明
详细说明见JS Crash异常代码调用栈格式。
增强日志聚类规则
聚类规则说明
在一份包含多个应用主线程堆栈(如10份)的日志中,对每份采样栈执行以下操作。
1.过滤系统栈
如需过滤掉系统栈帧(如 /system/lib/...、ld-musl 等)。系统栈帧格式示例:
# 00 pc 000e8400 /system/lib/ld-musl-arm.so.1(raise+176)(a40044d0acb68107cfc4adb5049c0725)
2.保留业务栈
如需保留业务栈帧(以“at”为起始字符,或包含“/data/storage”子串,JS 栈帧默认视为业务栈帧(来自应用代码)。业务栈帧格式示例:
at onPageShow har1 (har1/src/main/ets/pages/Index.ets:7:13)
3.标准化栈帧
定义标准化栈帧的内容,自行去除易变信息(如行号、字节偏移、BuildID),根据实际聚类需求保留聚类的关键信息。
对每一帧业务栈帧,执行以下清洗操作:
(1)Native栈帧标准化
| 原始栈帧内容 | 标准化后栈帧内容 |
|---|---|
| # 01 pc 00006e95 /data/crasher_cpp(DfxCrasher::RaiseSegmentFaultException()+92)(d6cead5be17c9bb7eee2a9b4df4b7626) | /data/crasher_cpp(DfxCrasher::RaiseSegmentFaultException()+92) |
按以下步骤处理:
a. 提取函数签名部分(括号内的内容,含类名、函数名、参数);
b. 忽略PC偏移和BuildID;
c. 保留函数完整签名(包括 const、参数类型等,若日志中已解析)。
(2)JS栈帧标准化
| 原始栈帧内容 | 标准化后栈帧内容 |
|---|---|
| # 00 at onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13) | onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13) |
按以下步骤处理:
a. 去除行号;
b. 保留函数名(如 onPageShow);
c. 保留文件路径(如 src/main/ets/pages/Index.ts);
d. 最终生成一个标准化的业务调用栈序列,用于后续聚类分析。
4.生成聚类特征
| 原始采样栈 | 最终聚类特征(调用顺序从上到下) |
|---|---|
|
# 00 pc 000e8400 /system/lib/ld-musl-arm.so.1(raise+176)(a40044d0...) # 01 pc 00006e95 /data/crasher_cpp(DfxCrasher::RaiseSegmentFaultException()+92)(d6ce...) # 02 pc 00008909 /data/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+612)(d6ce...) # 03 at onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13) |
/data/crasher_cpp(DfxCrasher::RaiseSegmentFaultException()+92) /data/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+612) at onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13) |
根据聚类特征直接聚类所有采样栈。
聚类脚本说明
该脚本的使用仅限于AppFreeze增强日志。当日志内容过长、主线程堆栈存在多次重复时,用于提取业务栈聚类信息(业务栈内容、总出现次数及代表性完整堆栈等),更快定位问题。
1.脚本功能
该脚本用于批量处理指定文件夹中的.zip日志文件。按以下步骤处理:
(1)从输入文件夹中读取所有.zip文件中采样栈文件;
(2)自动逐一解压,解析及转换;
(3)将处理结果输出到指定文件夹中。
2.运行方式
get_all_result(input_dir: str, output_dir: str)
| 参数名 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
| input_dir | 必填 | 字符串 | 输入文件夹路径,需包含若干.zip日志文件。 |
| output_dir | 必填 | 字符串 | 输出文件夹路径,脚本会将处理后的结果写入该目录。 |
示例:
- get_all_result(r"D:\log\input", r"D:\log\output")
3.输入要求
input_dir目录下应为若干.zip文件。
不限制文件名,可为嵌套结构(脚本可拓展支持.zip内还有.zip)。
zip文件中必须带采样栈日志文件。
4.输出说明
脚本会在output_dir中生成处理结果(例如):
output_dir/
├─stack_summary.txt
├─ 业务栈聚类.txt
└─ 业务栈聚类.xlsx
实际输出类型依脚本逻辑而定(解压文件/转换文件/汇总文本/JSON/CSV等)。
5.运行前准备
确保本机或部署环境满足以下条件:
(1)已安装Python 3.x。
(2)已安装脚本依赖(如 os/zipfile/pandas 等)。
(3)output_dir不存在时脚本会自动创建(若脚本未实现自动创建,请提前建好目录)。
(4)该脚本不支持在DevEco Studio中执行,请在已安装的Python环境下运行该脚本。
6.聚类脚本源码
参考官方课程。
更多推荐


所有评论(0)