订阅主线程超时事件(ArkTS)
简介
本文介绍如何使用HiAppEvent提供的ArkTS接口订阅主线程超时事件。接口的详细使用说明(参数限制、取值范围等)请参考@ohos.hiviewdfx.hiAppEvent。
接口说明
| 接口名 | 描述 |
|---|---|
| addWatcher(watcher: Watcher): AppEventPackageHolder | 添加应用事件观察者,以添加对应用事件的订阅。 |
| removeWatcher(watcher: Watcher): void | 移除应用事件观察者,以移除对应用事件的订阅。 |
开发步骤
添加事件观察者
以主线程超时事件订阅为例,说明开发步骤。
-
新建一个ArkTS应用工程,编辑工程中的“entry > src > main > ets > entryability > EntryAbility.ets”文件,导入依赖模块,示例代码如下:
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';import { buffer, util } from '@kit.ArkTS'import { fileIo as fs } from '@kit.CoreFileKit'; -
编辑工程中的“entry > src > main > ets > entryability > EntryAbility.ets”文件,可在onCreate、onForeground等生命周期接口中添加系统事件的订阅(结合业务需求,在合适的位置添加订阅方法),示例代码如下:
hiAppEvent.addWatcher({// 开发者可以自定义观察者名称,系统会使用名称来标识不同的观察者name: "watcher",// 开发者可以订阅感兴趣的系统事件,此处是订阅了主线程超时事件appEventFilters: [{domain: hiAppEvent.domain.OS,names: [hiAppEvent.event.MAIN_THREAD_JANK]}],// 开发者可以自行实现订阅实时回调函数,以便对订阅获取到的事件数据进行自定义处理onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {hilog.info(0x0000, 'testTag', `HiAppEvent onReceive: domain=${domain}`);for (const eventGroup of appEventGroups) {// 开发者可以根据事件集合中的事件名称区分不同的系统事件hilog.info(0x0000, 'testTag', `HiAppEvent eventName=${eventGroup.name}`);for (const eventInfo of eventGroup.appEventInfos) {// 开发者可以对事件集合中的事件数据进行自定义处理,此处是将事件数据打印在日志中hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.domain=${eventInfo.domain}`);hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.name=${eventInfo.name}`);hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.eventType=${eventInfo.eventType}`);// 开发者可以获取到主线程超时事件发生的时间戳hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.time=${eventInfo.params['time']}`);// 开发者可以获取到主线程超时应用的版本信息hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.bundle_version=${eventInfo.params['bundle_version']}`);// 开发者可以获取到主线程超时应用的包名hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.bundle_name=${eventInfo.params['bundle_name']}`);// 开发者可以获取到主线程超时应用的pid、uidhilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.pid=${eventInfo.params['pid']}`);hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.uid=${eventInfo.params['uid']}`);// 开发者可以获取主线程处理开始和结束时间hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.begin_time=${eventInfo.params['begin_time']}`);hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.end_time=${eventInfo.params['end_time']}`);hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.log_over_limit=${eventInfo.params['log_over_limit']}`);// 开发者可以获取到主线程超时事件时,任务执行的开始时间(主线程超时采集堆栈参数)hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.app_start_jiffies_time=${JSON.stringify(eventInfo.params['app_start_jiffies_time'])}`);// 开发者可以获取到生成的主线程超时日志文件中,打印最多次的调用栈(主线程超时采集堆栈参数)hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.heaviest_stack=${eventInfo.params['heaviest_stack']}`);// 开发者可以获取到主线程超时事件发生时的故障日志文件hilog.info(0x0000, 'testTag', `HiAppEvent eventInfo.params.external_log=${JSON.stringify(eventInfo.params['external_log'])}`);// 开发者可以通过以下方式移动文件到新的目录let path: string = String(eventInfo.params['external_log']);// 自定义的新的存储路径let targetPath: string = "";if (path.endsWith(".txt")) {targetPath= "/data/storage/el2/base/mainThreadJank.txt";} else if (path.endsWith(".trace")) {targetPath= "/data/storage/el2/base/mainThreadJank.trace";}fs.copyFileSync(path.toString(), targetPath.toString());}}}}); -
该步骤用于模拟主线程超时采样栈事件。
编辑工程中的“entry > src > main > ets > pages> Index.ets”文件,示例代码如下:
@Entry@Componentstruct Index {build() {RelativeContainer() {Column() {Button("timeOut350", { stateEffect:true, type: ButtonType.Capsule}).width('75%').height(50).margin(15).fontSize(20).fontWeight(FontWeight.Bold).onClick(() => {let t = Date.now();while (Date.now() - t <= 350) {}})}.width('100%')}.height('100%').width('100%')}} -
(可选)该步骤用于模拟自定义主线程超时参数,并触发主线程超时事件场景。
编辑工程中的“entry > src > main > ets > pages> Index.ets”文件,本示例中设置一个customSample的Button控件,在onClick中实现自定义设置采样栈参数代码,示例代码如下:
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';import { BusinessError } from '@kit.BasicServicesKit';// 模拟超时事件函数定义,示例代码:function wait150ms() {let t = Date.now();while (Date.now() - t <= 150){}}function wait500ms() {let t = Date.now();while (Date.now() - t <= 500){}}@Entry@Componentstruct Index {build() {RelativeContainer() {Column() {// 自定义设置采样栈参数按钮Button("customSample", { stateEffect:true, type: ButtonType.Capsule}).width('75%').height(50).margin(15).fontSize(20).fontWeight(FontWeight.Bold).onClick(() => {// 在按钮点击函数中进行事件打点,以记录按钮点击事件let params: Record<string, hiAppEvent.ParamType> = {// 事件类型定义, 0-默认值,1-只采样栈 2-只收集trace"log_type": "1",// 超时时间 & 采样间隔"sample_interval": "100",// 忽略启动开始时间"ignore_startup_time": "11",// 采样次数"sample_count": "21",// 事件上报次数定义"report_times_per_app": "3"};hiAppEvent.setEventConfig(hiAppEvent.event.MAIN_THREAD_JANK, params).then(() => {hilog.info(0x0000, 'testTag', `HiAppEvent success to set event params.`)}).catch((err: BusinessError) => {hilog.error(0x0000, 'testTag', `HiAppEvent err.code: ${err.code}, err.message: ${err.message}`)});})// 触发150ms超时事件按钮Button("timeOut150", { stateEffect:true, type: ButtonType.Capsule}).width('75%').height(50).margin(15).fontSize(20).fontWeight(FontWeight.Bold).onClick(() => {wait150ms();})}.width('100%')}.height('100%').width('100%')}} -
该步骤可用于模拟主线程超时采样trace事件。
编辑工程中的“entry > src > main > ets > pages> Index.ets”文件,添加按钮并在其onClick函数触发主线程超时采集trace功能,具体如下:
启动主线程超时检测抓取trace的功能的前提是开发者使用nolog版本并且关闭开发者模式。
@Entry@Componentstruct Index {build() {RelativeContainer() {Column() {Button("timeOut500", { stateEffect:true, type: ButtonType.Capsule}).width('75%').height(50).margin(15).fontSize(20).fontWeight(FontWeight.Bold).onClick(() => {let t = Date.now();while (Date.now() - t <= 500) {}})}.width('100%')}.height('100%').width('100%')}} -
点击DevEco Studio界面中的运行按钮,运行应用工程。
默认情况下,由于应用启动过程本身较为耗时,系统将在应用启动10s后再进行测试,开始主线程超时事件检测;
若开发者使用setEventConfig接口设置自定义设置采样栈参数,系统将在开发者设定的ignore_startup_time时间后,开始主线程超时事件检测。
主线程超时触发的条件:在检测任务的间隔内,检测到连续两次超时事件后,才会开启采集堆栈。
用户可以快速点击2~3次触发超时的按钮(如:timeOut350、timeOut150或timeOut500三种不同卡顿场景的按钮),以触发主线程超时事件。
验证观察者是否订阅到主线程超时事件
-
主线程超时事件上报后,系统会回调应用的onReceive函数,可以在Log窗口看到对系统事件数据的处理日志:
主线程超时事件采样栈示例:
HiAppEvent eventInfo.domain=OSHiAppEvent eventInfo.name=MAIN_THREAD_JANKHiAppEvent eventInfo.eventType=1HiAppEvent eventInfo.params.time=1717593620518HiAppEvent eventInfo.params.bundle_version=1.0.0HiAppEvent eventInfo.params.bundle_name=com.example.main_thread_jankHiAppEvent eventInfo.params.pid=40986HiAppEvent eventInfo.params.uid=20020150HiAppEvent eventInfo.params.begin_time=1717593620016HiAppEvent eventInfo.params.end_time=1717593620518HiAppEvent eventInfo.params.external_log=["/data/storage/el2/log/watchdog/MAIN_THREAD_JANK_20240613211739_40986.XXX"]HiAppEvent eventInfo.params.log_over_limit=falseHiAppEvent eventInfo.params.app_start_jiffies_time=XXXXHiAppEvent eventInfo.params.heaviest_stack=XXXX主线程超时事件采样trace,与采样栈的结果大致相同,不同的地方:
栈:采样栈增加两个参数:app_start_jiffies_time和heaviest_stack。external_log=["/data/storage/el2/log/watchdog/MAIN_THREAD_JANK_yyyyMMDDHHmmss_xxxx.txt"]。xxxx:代表进程pidtrace:external_log=["/data/storage/el2/log/watchdog/MAIN_THREAD_JANK_unix时间戳_xxxx.trace"]。xxxx:代表进程pid