feat: 启动监控扩展追溯字段并修复补报事件语义

- StartupMonitor 上报新增时间拆解与客户端环境字段:response_latency、startability_processstart_dur、appattach_to_appforeground_dur、bundle_name、device_type、system_version、build_mode、target_name、git_commit、is_first_install
- 新增 is_backfill 补报标识:以 watcher 注册时间为锚点判定系统补投递的上次启动遗留事件,entry_page 置 unknown,避免错误归因
- CrashDiagnosticsDemo 同步字段展示与补报判定逻辑,devecocli build 构建通过
- 真机验证(nova 14 Pro):冷启动/多入口拦截/热启动/5 秒退出/补报判定/全场景 3 组全部通过,耗时 126~180ms
- 接入方案文档更新:第 4 节追溯字段与时间拆解、第 6 节 5 秒退出边界、第 8 节验证记录

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
clz
2026-08-05 19:09:22 +08:00
co-authored by Claude
parent d0949704ed
commit 10bbfc4c82
3 changed files with 248 additions and 6 deletions
@@ -6,6 +6,7 @@
import { common } from '@kit.AbilityKit';
import { inspector } from '@kit.ArkUI';
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';
import { deviceInfo } from '@kit.BasicServicesKit';
enum DrawReportState {
IDLE,
@@ -19,11 +20,27 @@ enum DrawReportState {
*
* 监听系统 APP_LAUNCH 事件,并确保一个进程生命周期内只提交一次
* reportDrawnCompleted。检测结果同时输出到 hilog 和模拟页面。
*
* 与 future_entry_demo 的 StartupMonitor.ets 对应:本文件为无依赖演示实现
* (仅 hilog + 页面展示,不做 ELK 上传),追溯字段已同步系统时间拆解字段
* 与 deviceInfo 环境字段;git_commit / is_first_install / build_mode 依赖
* biz_common 与 BuildProfile,演示工程不适用,故未同步。
*/
export class StartupDiagnostics {
private static readonly WATCHER_NAME: string = 'startupDiagnosticsWatcher';
private static readonly LOG_DOMAIN: number = 0xD002;
private static readonly MAX_EVENT_KEYS: number = 20;
/**
* 补报事件判定容差(ms)。
* 本次启动链中 icon_input_time(点击图标)与 watcher 注册时间间隔在毫秒~秒级;
* 系统补投递的上次启动遗留事件,其 icon_input_time 至少早于注册时间 5 秒(事件送达延迟),
* 因此容差取 2s 可稳定区分两者。
*/
private static readonly BACKFILL_TOLERANCE_MS: number = 2000;
/**
* watcher 注册时间戳(epoch ms),用于识别系统补投递的上次启动遗留事件。
*/
private static watcherRegisterTime: number = 0;
private static watcherInitialized: boolean = false;
private static drawReportState: DrawReportState = DrawReportState.IDLE;
private static capturedEntryPage: string = '未捕获';
@@ -59,6 +76,9 @@ export class StartupDiagnostics {
}
};
// 记录注册时间戳,用于区分本次启动事件与系统补投递的上次启动遗留事件
StartupDiagnostics.watcherRegisterTime = Date.now();
try {
hiAppEvent.addWatcher(watcher);
StartupDiagnostics.watcherInitialized = true;
@@ -230,15 +250,52 @@ export class StartupDiagnostics {
StartupDiagnostics.processedEventKeys.shift();
}
const bundleName: string = (params['bundle_name'] as string) ?? '';
const bundleVersion: string = (params['bundle_version'] as string) ?? '';
const animationFinishTime: number = (params['animation_finish_time'] as number) ?? 0;
// 时间拆解字段(与接入方案文档第 4 节一致):
// response_latency 需 API 22+;startability_processstart_dur / appattach_to_appforeground_dur 仅冷启动存在
const responseLatency: number = (params['response_latency'] as number) ?? 0;
const startabilityProcessStartDur: number = (params['startability_processstart_dur'] as number) ?? 0;
const appattachToAppForegroundDur: number = (params['appattach_to_appforeground_dur'] as number) ?? 0;
// 补投递事件(上次启动提交绘制但进程在事件送达前退出)的 capturedEntryPage 是当前进程的快照,
// 不能错误归因到本次启动的页面,因此补报事件的入口标记为未知。
const isBackfill: boolean = StartupDiagnostics.isBackfillEvent(iconInputTime);
StartupDiagnostics.launchEventCount++;
StartupDiagnostics.lastLaunchDetail =
`extend_time=${extendTime}ms, icon_input_time=${iconInputTime}, ` +
`start_type=${startType}, process_name=${processName}`;
`start_type=${startType}, process_name=${processName}\n` +
`bundle_name=${bundleName}, bundle_version=${bundleVersion}\n` +
`animation_finish_time=${animationFinishTime}ms, response_latency=${responseLatency}ms\n` +
`startability_processstart_dur=${startabilityProcessStartDur}ms, ` +
`appattach_to_appforeground_dur=${appattachToAppForegroundDur}ms\n` +
`device_type=${StartupDiagnostics.getDeviceType()}, ` +
`system_version=${StartupDiagnostics.getSystemVersion()}\n` +
`补报事件=${isBackfill ? '是(entry_page 不归因,按 unknown 处理)' : '否'}`;
StartupDiagnostics.lastAction = '已捕获有效冷启动 APP_LAUNCH';
StartupDiagnostics.logInfo(StartupDiagnostics.lastLaunchDetail);
StartupDiagnostics.notifyStatusChanged();
}
/**
* 判断事件是否为系统补投递的上次启动遗留事件。
* 本次启动链中 icon_input_time 与 watcher 注册时间间隔在容差内;
* 补报事件的 icon_input_time 早于注册时间超过容差(至少 5 秒的送达延迟)。
*/
private static isBackfillEvent(iconInputTime: number): boolean {
return iconInputTime < StartupDiagnostics.watcherRegisterTime - StartupDiagnostics.BACKFILL_TOLERANCE_MS;
}
private static getDeviceType(): string {
return deviceInfo.deviceType;
}
private static getSystemVersion(): string {
return deviceInfo.osFullName;
}
private static getStateName(): string {
switch (StartupDiagnostics.drawReportState) {
case DrawReportState.IDLE: