应用闪退是一个非常严重的问题,当我们做性能分析时,需要获取到用户的崩溃信息,查明崩溃原因。以下两种情况会导致程序异常退出:
1.Native代码未处理异常时
2.Js/ArkTS代码未处理异常时
接下来,主要讲述一下获取ArkTS层的崩溃信息。
HiAppEvent是在系统层面为应用开发者提供的一种事件打点机制,帮助应用记录在运行过程中发生的故障信息、统计信息、安全信息、用户行为信息,支撑开发者分析应用的运行情况
实现过程:
1.创建订阅者 Watcher配置和管理事件的观察者
名称 | 说明 |
---|---|
name | 观察者名称,用于唯一标识观察者 |
triggerCondition | 订阅回调触发条件 |
appEventFilters | 订阅过滤条件 |
onTrigger | 订阅回调函数 |
onReceive | 订阅实时回调函数 |
let watcher: hiAppEvent.Watcher = {
// 自定义观察者名称,首字符必须为字母字符,中间字符必须为数字字符、字母字符或下划线字符,结尾字符必须为数字字符或字母字符
name: "watcher",
// 订阅了崩溃事件
appEventFilters: [
{
domain: hiAppEvent.domain.OS, //系统事件领域
names: [hiAppEvent.event.APP_CRASH]
}
],
// 实时回调函数
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
}
};
hiAppEvent.addWatcher(watcher);
2.处理回调信息AppEventGroup
名称 | 说明 |
---|---|
domain | 事件领域 |
name | 事件名称 |
eventType | 事件类型 |
params | 系统事件中params包含的字段已由各系统事件定义 |
3.params字段解析,存储到自定义的一个事件对象中
名称 | 说明 |
---|---|
time | 事件触发时间,单位为毫秒。 |
crash_type | 崩溃类型 |
foreground | 是否处于前台状态 |
bundle_version | 应用版本 |
bundle_name | 应用名称 |
pid | 应用的进程ID |
uid | 应用的用户ID |
uuid | 根据故障信息生成的故障特征码 |
exception | 异常信息 |
hilog | 日志信息,最多显示100行hilog日志 |
threads | 仅NativeCrash类型的崩溃事件提供 |
external_log | 故障日志文件路径 |
log_over_limit | 生成的故障日志文件与已存在的日志文件总大小是否超过5MB上限 |
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
for (const eventGroup of appEventGroups) {
//watcher中我只订阅了崩溃事件
for (const eventInfo of eventGroup.appEventInfos) {
let eventInfoParam:EventInfoParam = {
domain: eventInfo.domain,
eventType: eventInfo.eventType,
time: eventInfo.params['time'],
crash_type: eventInfo.params['crash_type'],
foreground: eventInfo.params['foreground'],
bundle_version: eventInfo.params['bundle_version'],
bundle_name: eventInfo.params['bundle_name'],
pid: eventInfo.params['pid'],
uid: eventInfo.params['uid'],
uuid: eventInfo.params['uuid'],
exception_name: eventInfo.params['exception']['name'],
exception_message: eventInfo.params['exception']['message'],
exception_stack: eventInfo.params['exception']['stack'],
size: eventInfo.params['hilog'].length,
external_log: JSON.stringify(eventInfo.params['external_log']),
log_over_limit: eventInfo.params['log_over_limit'],
}
AppStorage.setOrCreate('EventInfoParam', eventInfoParam);
}
}
}
4.触发机制:当应用发生闪退后,再次打开会获取上次闪退的日志信息。
5.触发闪退,查看日志
I =========EntryAbility: onCreate()
I =========EntryAbility: onWindowStageCreate()
I =========EntryAbility: onForeground()
I =========EntryAbility: 获取到闪退日志
I =========EntryAbility: 上传日志
由上可知,应用重启获取闪退日志是在程序进入到前台之后
闪退日志信息:
以上可以看到JsError异常的详细信息,基本可以帮我们定位到闪退原因。
补充一下JS Crash异常类型
名称 | 说明 |
---|---|
TypeError | 类型错误,运行时最常见的异常,表示变量或参数不是预期类型 |
SyntaxError | 语法错误,表示不符合编程语言的语法规范。 |
RangeError | 超出有效范围时发生的异常,列如数字类型的方法参数超出预定义范围 |
ReferenceError | 引用一个不存在的变量时发生的错误 |
URI Error | 在调用URI相关的方法中URL无效时抛出的异常 |
源码: |
EntryAbility
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.log('=========EntryAbility:','onCreate()')
let watcher: hiAppEvent.Watcher = {
// 自定义观察者名称,首字符必须为字母字符,中间字符必须为数字字符、字母字符或下划线字符,结尾字符必须为数字字符或字母字符
name: "watcher",
// 订阅了崩溃事件
appEventFilters: [
{
domain: hiAppEvent.domain.OS, //系统事件领域
names: [hiAppEvent.event.APP_CRASH]
}
],
// 实时回调函数
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
for (const eventGroup of appEventGroups) {
for (const eventInfo of eventGroup.appEventInfos) {
let eventInfoParam:EventInfoParam = {
domain: eventInfo.domain,
eventType: eventInfo.eventType,
time: eventInfo.params['time'],
crash_type: eventInfo.params['crash_type'],
foreground: eventInfo.params['foreground'],
bundle_version: eventInfo.params['bundle_version'],
bundle_name: eventInfo.params['bundle_name'],
pid: eventInfo.params['pid'],
uid: eventInfo.params['uid'],
uuid: eventInfo.params['uuid'],
exception_name: eventInfo.params['exception']['name'],
exception_message: eventInfo.params['exception']['message'],
exception_stack: eventInfo.params['exception']['stack'],
size: eventInfo.params['hilog'].length,
external_log: JSON.stringify(eventInfo.params['external_log']),
log_over_limit: eventInfo.params['log_over_limit'],
}
AppStorage.setOrCreate('EventInfoParam', eventInfoParam);
}
}
setTimeout(()=>{
console.log('=========EntryAbility:','上传日志')
},3000)
}
};
hiAppEvent.addWatcher(watcher);
}
import { EventInfoParam } from '../model/EventInfoParam'
@Entry
@ComponentV2
struct EventInfoTest{
eventInfoParam:EventInfoParam|undefined = AppStorage.get('EventInfoParam')
build() {
Scroll(){
Column(){
Text('JsError崩溃信息:')
Text('事件领域:'+this.eventInfoParam?.domain)
Text('事件类型:'+this.eventInfoParam?.crash_type)
Text('事件触发时间:'+this.eventInfoParam?.time)
Text('是否处于前台状态:'+this.eventInfoParam?.foreground)
Text('应用版本:'+this.eventInfoParam?.bundle_version)
Text('应用名称:bundleName')
Text('进程ID:'+this.eventInfoParam?.pid)
Text('用户ID:'+this.eventInfoParam?.uid)
Text('uuid:根据故障信息生成的故障特征码,用于标识特征相同的崩溃故障')
Text('JsError异常类型:'+this.eventInfoParam?.exception_name)
Text('JsError异常原因:'+this.eventInfoParam?.exception_message)
Text('JsError异常调用栈:'+this.eventInfoParam?.exception_stack)
Text('日志信息:最多显示100行hilog日志')
Text('故障日志文件路径:'+this.eventInfoParam?.external_log)
Text('生成的故障日志文件与已存在的日志文件总大小是否超过5MB上限:'+this.eventInfoParam?.log_over_limit)
}
.alignItems(HorizontalAlign.Start)
}.height('100%')
}
}
export interface EventInfoParam {
domain: string
eventType: number
time: number
crash_type: string
foreground: boolean
bundle_version: string
bundle_name: string
pid: number
uid: number
uuid: string
exception_name: string
exception_message: string
exception_stack: string
size: number
external_log: string
log_over_limit: boolean
}