feat: 完善启动性能监控流程
- 增加登录、网络异常和隐私确认启动场景 - 持久化登录状态并支持首页退出登录 - 保证每个进程只提交一次 reportDrawnCompleted - 按启动事件唯一键去重 APP_LAUNCH 上报 - 同步优化逻辑到 future_entry_demo 并在 CrashDiagnosticsDemo 真机验证
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { inspector } from '@kit.ArkUI';
|
||||
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';
|
||||
|
||||
enum DrawReportState {
|
||||
IDLE,
|
||||
SUBMITTED,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动性能检测演示。
|
||||
*
|
||||
* 监听系统 APP_LAUNCH 事件,并确保一个进程生命周期内只提交一次
|
||||
* reportDrawnCompleted。检测结果同时输出到 hilog 和模拟页面。
|
||||
*/
|
||||
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;
|
||||
private static watcherInitialized: boolean = false;
|
||||
private static drawReportState: DrawReportState = DrawReportState.IDLE;
|
||||
private static capturedEntryPage: string = '未捕获';
|
||||
private static activeObserverOwner: string = '';
|
||||
private static activeDisposer: (() => void) | undefined = undefined;
|
||||
private static statusListenerOwner: string = '';
|
||||
private static statusUpdatedListener: (() => void) | undefined = undefined;
|
||||
private static processedEventKeys: Array<string> = [];
|
||||
private static drawCallbackCount: number = 0;
|
||||
private static reportSubmitCount: number = 0;
|
||||
private static skippedReportCount: number = 0;
|
||||
private static launchEventCount: number = 0;
|
||||
private static duplicateEventCount: number = 0;
|
||||
private static lastAction: string = '等待登录页首次绘制';
|
||||
private static lastLaunchDetail: string = '尚未收到 APP_LAUNCH';
|
||||
|
||||
static initialize(): void {
|
||||
if (StartupDiagnostics.watcherInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filter: hiAppEvent.AppEventFilter = {
|
||||
domain: hiAppEvent.domain.OS,
|
||||
names: [hiAppEvent.event.APP_LAUNCH]
|
||||
};
|
||||
const watcher: hiAppEvent.Watcher = {
|
||||
name: StartupDiagnostics.WATCHER_NAME,
|
||||
appEventFilters: [filter],
|
||||
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
|
||||
hilog.info(StartupDiagnostics.LOG_DOMAIN, 'StartupDiagnostics',
|
||||
'APP_LAUNCH onReceive domain=%{public}s, groups=%{public}d', domain, appEventGroups.length);
|
||||
StartupDiagnostics.processAppLaunchEvents(appEventGroups);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
hiAppEvent.addWatcher(watcher);
|
||||
StartupDiagnostics.watcherInitialized = true;
|
||||
StartupDiagnostics.lastAction = 'APP_LAUNCH 监听已注册,等待首帧';
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
} catch (err) {
|
||||
StartupDiagnostics.lastAction = `APP_LAUNCH 监听注册失败:${StartupDiagnostics.getErrorMessage(err)}`;
|
||||
StartupDiagnostics.logError(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static reportFirstFrameOnDraw(
|
||||
createObserver: () => inspector.ComponentObserver,
|
||||
abilityContext: common.UIAbilityContext,
|
||||
pageName: string
|
||||
): void {
|
||||
if (StartupDiagnostics.drawReportState !== DrawReportState.IDLE) {
|
||||
StartupDiagnostics.skippedReportCount++;
|
||||
StartupDiagnostics.lastAction = `页面 ${pageName} 的重复首帧注册已跳过`;
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
StartupDiagnostics.disposeFirstFrame(StartupDiagnostics.activeObserverOwner);
|
||||
|
||||
try {
|
||||
const observer: inspector.ComponentObserver = createObserver();
|
||||
const onDraw = (): void => {
|
||||
observer.off('draw', onDraw);
|
||||
if (StartupDiagnostics.activeObserverOwner === pageName) {
|
||||
StartupDiagnostics.activeObserverOwner = '';
|
||||
StartupDiagnostics.activeDisposer = undefined;
|
||||
}
|
||||
|
||||
if (StartupDiagnostics.drawReportState !== DrawReportState.IDLE) {
|
||||
StartupDiagnostics.skippedReportCount++;
|
||||
StartupDiagnostics.lastAction = `页面 ${pageName} 的重复 draw 回调已跳过`;
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
StartupDiagnostics.drawCallbackCount++;
|
||||
StartupDiagnostics.drawReportState = DrawReportState.SUBMITTED;
|
||||
StartupDiagnostics.capturedEntryPage = pageName;
|
||||
StartupDiagnostics.reportSubmitCount++;
|
||||
StartupDiagnostics.lastAction = `已提交 ${pageName} 的 reportDrawnCompleted`;
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
|
||||
try {
|
||||
abilityContext.reportDrawnCompleted((err) => {
|
||||
if (err && err.code !== 0) {
|
||||
StartupDiagnostics.drawReportState = DrawReportState.FAILED;
|
||||
StartupDiagnostics.lastAction =
|
||||
`reportDrawnCompleted 失败:code=${err.code}, message=${err.message}`;
|
||||
StartupDiagnostics.logError(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
StartupDiagnostics.drawReportState = DrawReportState.SUCCEEDED;
|
||||
StartupDiagnostics.lastAction = `reportDrawnCompleted 成功:${pageName}`;
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
});
|
||||
} catch (err) {
|
||||
// 同步异常表示异步任务未成功提交,恢复 IDLE 后允许其他页面重试。
|
||||
StartupDiagnostics.drawReportState = DrawReportState.IDLE;
|
||||
StartupDiagnostics.lastAction =
|
||||
`reportDrawnCompleted 同步异常:${StartupDiagnostics.getErrorMessage(err)}`;
|
||||
StartupDiagnostics.logError(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
}
|
||||
};
|
||||
|
||||
StartupDiagnostics.activeObserverOwner = pageName;
|
||||
StartupDiagnostics.activeDisposer = (): void => {
|
||||
observer.off('draw', onDraw);
|
||||
};
|
||||
observer.on('draw', onDraw);
|
||||
StartupDiagnostics.lastAction = `已监听 ${pageName} 根组件 draw`;
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
} catch (err) {
|
||||
StartupDiagnostics.lastAction = `创建 draw 监听失败:${StartupDiagnostics.getErrorMessage(err)}`;
|
||||
StartupDiagnostics.logError(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static disposeFirstFrame(pageName: string): void {
|
||||
if (pageName === '' || StartupDiagnostics.activeObserverOwner !== pageName) {
|
||||
return;
|
||||
}
|
||||
const disposer: (() => void) | undefined = StartupDiagnostics.activeDisposer;
|
||||
if (disposer !== undefined) {
|
||||
disposer();
|
||||
}
|
||||
StartupDiagnostics.activeObserverOwner = '';
|
||||
StartupDiagnostics.activeDisposer = undefined;
|
||||
}
|
||||
|
||||
static setStatusUpdatedListener(owner: string, listener: () => void): void {
|
||||
StartupDiagnostics.statusListenerOwner = owner;
|
||||
StartupDiagnostics.statusUpdatedListener = listener;
|
||||
listener();
|
||||
}
|
||||
|
||||
static clearStatusUpdatedListener(owner: string): void {
|
||||
if (StartupDiagnostics.statusListenerOwner !== owner) {
|
||||
return;
|
||||
}
|
||||
StartupDiagnostics.statusListenerOwner = '';
|
||||
StartupDiagnostics.statusUpdatedListener = undefined;
|
||||
}
|
||||
|
||||
static getStatusText(): string {
|
||||
return `状态:${StartupDiagnostics.getStateName()}\n` +
|
||||
`捕获入口:${StartupDiagnostics.capturedEntryPage}\n` +
|
||||
`draw 回调:${StartupDiagnostics.drawCallbackCount}\n` +
|
||||
`系统提交:${StartupDiagnostics.reportSubmitCount}\n` +
|
||||
`重复拦截:${StartupDiagnostics.skippedReportCount}\n` +
|
||||
`APP_LAUNCH:${StartupDiagnostics.launchEventCount}\n` +
|
||||
`事件去重:${StartupDiagnostics.duplicateEventCount}\n` +
|
||||
`最近动作:${StartupDiagnostics.lastAction}\n` +
|
||||
`事件详情:${StartupDiagnostics.lastLaunchDetail}`;
|
||||
}
|
||||
|
||||
private static processAppLaunchEvents(appEventGroups: Array<hiAppEvent.AppEventGroup>): void {
|
||||
for (let groupIndex: number = 0; groupIndex < appEventGroups.length; groupIndex++) {
|
||||
const group: hiAppEvent.AppEventGroup = appEventGroups[groupIndex];
|
||||
for (let infoIndex: number = 0; infoIndex < group.appEventInfos.length; infoIndex++) {
|
||||
StartupDiagnostics.processAppLaunchEvent(group.appEventInfos[infoIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static processAppLaunchEvent(eventInfo: hiAppEvent.AppEventInfo): void {
|
||||
const paramsJson: string = JSON.stringify(eventInfo.params);
|
||||
const params: Record<string, Object> = JSON.parse(paramsJson) as Record<string, Object>;
|
||||
const startType: number = (params['start_type'] as number) ?? -1;
|
||||
if (startType !== 0) {
|
||||
StartupDiagnostics.logInfo(`跳过非冷启动事件:start_type=${startType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const extendTime: number = (params['extend_time'] as number) ?? 0;
|
||||
if (extendTime <= 0) {
|
||||
StartupDiagnostics.logInfo('跳过尚未填充 extend_time 的 APP_LAUNCH');
|
||||
return;
|
||||
}
|
||||
|
||||
const eventTime: number = (params['time'] as number) ?? 0;
|
||||
const iconInputTime: number = (params['icon_input_time'] as number) ?? 0;
|
||||
const processName: string = (params['process_name'] as string) ?? '';
|
||||
const eventKey: string = `${eventTime}|${iconInputTime}|${startType}|${processName}`;
|
||||
if (StartupDiagnostics.processedEventKeys.indexOf(eventKey) !== -1) {
|
||||
StartupDiagnostics.duplicateEventCount++;
|
||||
StartupDiagnostics.lastAction = `重复 APP_LAUNCH 已拦截:${eventKey}`;
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastAction);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
StartupDiagnostics.processedEventKeys.push(eventKey);
|
||||
if (StartupDiagnostics.processedEventKeys.length > StartupDiagnostics.MAX_EVENT_KEYS) {
|
||||
StartupDiagnostics.processedEventKeys.shift();
|
||||
}
|
||||
|
||||
StartupDiagnostics.launchEventCount++;
|
||||
StartupDiagnostics.lastLaunchDetail =
|
||||
`extend_time=${extendTime}ms, icon_input_time=${iconInputTime}, ` +
|
||||
`start_type=${startType}, process_name=${processName}`;
|
||||
StartupDiagnostics.lastAction = '已捕获有效冷启动 APP_LAUNCH';
|
||||
StartupDiagnostics.logInfo(StartupDiagnostics.lastLaunchDetail);
|
||||
StartupDiagnostics.notifyStatusChanged();
|
||||
}
|
||||
|
||||
private static getStateName(): string {
|
||||
switch (StartupDiagnostics.drawReportState) {
|
||||
case DrawReportState.IDLE:
|
||||
return 'IDLE';
|
||||
case DrawReportState.SUBMITTED:
|
||||
return 'SUBMITTED';
|
||||
case DrawReportState.SUCCEEDED:
|
||||
return 'SUCCEEDED';
|
||||
case DrawReportState.FAILED:
|
||||
return 'FAILED';
|
||||
default:
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
private static notifyStatusChanged(): void {
|
||||
const listener: (() => void) | undefined = StartupDiagnostics.statusUpdatedListener;
|
||||
if (listener !== undefined) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
private static getErrorMessage(err: Object): string {
|
||||
return err instanceof Error ? err.message : JSON.stringify(err);
|
||||
}
|
||||
|
||||
private static logInfo(message: string): void {
|
||||
hilog.info(StartupDiagnostics.LOG_DOMAIN, 'StartupDiagnostics', '%{public}s', message);
|
||||
}
|
||||
|
||||
private static logError(message: string): void {
|
||||
hilog.error(StartupDiagnostics.LOG_DOMAIN, 'StartupDiagnostics', '%{public}s', message);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import { AbilityConstant, common, ConfigurationConstant, UIAbility, Want } from
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { window } from '@kit.ArkUI';
|
||||
import { CrashDiagnostics } from '../diagnostics/CrashDiagnostics';
|
||||
import { StartupDiagnostics } from '../diagnostics/StartupDiagnostics';
|
||||
import { AuthSession } from '../session/AuthSession';
|
||||
|
||||
const DOMAIN = 0x0000;
|
||||
|
||||
@@ -24,6 +26,8 @@ export default class EntryAbility extends UIAbility {
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||
try {
|
||||
CrashDiagnostics.initialize(this.context as common.UIAbilityContext);
|
||||
StartupDiagnostics.initialize();
|
||||
AuthSession.initialize(this.context as common.UIAbilityContext);
|
||||
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
@@ -42,7 +46,8 @@ export default class EntryAbility extends UIAbility {
|
||||
// Main window is created, set main page for this ability
|
||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||
|
||||
windowStage.loadContent('pages/Index', (err) => {
|
||||
const initialPage: string = AuthSession.isLoggedIn() ? 'pages/Index' : 'pages/StartupLogin';
|
||||
windowStage.loadContent(initialPage, (err) => {
|
||||
if (err.code) {
|
||||
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
|
||||
return;
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { CrashDiagnostics } from '../diagnostics/CrashDiagnostics';
|
||||
import { StartupDiagnostics } from '../diagnostics/StartupDiagnostics';
|
||||
import { AuthSession } from '../session/AuthSession';
|
||||
import crashNative from 'libentry.so';
|
||||
|
||||
const ENABLE_DEBUG_CRASH_DIAGNOSTICS: boolean = true;
|
||||
const PAGE_NAME: string = 'Index';
|
||||
let startupCrashDialogShown: boolean = false;
|
||||
|
||||
@Entry
|
||||
@@ -16,6 +20,11 @@ struct Index {
|
||||
@State recordCount: number = 0;
|
||||
|
||||
aboutToAppear(): void {
|
||||
StartupDiagnostics.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('STARTUP_HOME_ROOT'),
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext,
|
||||
PAGE_NAME
|
||||
);
|
||||
CrashDiagnostics.setRecordsUpdatedListener(() => {
|
||||
this.updateRecordStatus();
|
||||
this.showLastCrashIfNeeded();
|
||||
@@ -25,9 +34,25 @@ struct Index {
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupDiagnostics.disposeFirstFrame(PAGE_NAME);
|
||||
CrashDiagnostics.clearRecordsUpdatedListener();
|
||||
}
|
||||
|
||||
private logout(): void {
|
||||
if (!AuthSession.setLoggedIn(false)) {
|
||||
this.statusText = '退出登录失败:无法保存登录状态';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.getUIContext().getRouter().replaceUrl({ url: 'pages/StartupLogin' }).catch((err: Error) => {
|
||||
this.statusText = `退出登录后跳转失败:${err.message}`;
|
||||
});
|
||||
} catch (err) {
|
||||
const message: string = err instanceof Error ? err.message : JSON.stringify(err);
|
||||
this.statusText = `退出登录同步异常:${message}`;
|
||||
}
|
||||
}
|
||||
|
||||
private updateRecordStatus(): void {
|
||||
this.recordCount = CrashDiagnostics.getRecentRecords().length;
|
||||
this.statusText = this.recordCount === 0 ?
|
||||
@@ -123,8 +148,19 @@ struct Index {
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/CrashHistory' });
|
||||
})
|
||||
|
||||
Button('退出登录')
|
||||
.width('100%')
|
||||
.height(44)
|
||||
.fontSize(16)
|
||||
.borderRadius(8)
|
||||
.backgroundColor('#666666')
|
||||
.onClick(() => {
|
||||
this.logout();
|
||||
})
|
||||
}
|
||||
}
|
||||
.id('STARTUP_HOME_ROOT')
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { StartupDiagnostics } from '../diagnostics/StartupDiagnostics';
|
||||
import { AuthSession } from '../session/AuthSession';
|
||||
|
||||
const PAGE_NAME: string = 'StartupLogin';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct StartupLogin {
|
||||
@State account: string = 'demo@example.com';
|
||||
@State password: string = '123456';
|
||||
@State startupStatus: string = StartupDiagnostics.getStatusText();
|
||||
|
||||
aboutToAppear(): void {
|
||||
StartupDiagnostics.setStatusUpdatedListener(PAGE_NAME, () => {
|
||||
this.startupStatus = StartupDiagnostics.getStatusText();
|
||||
});
|
||||
StartupDiagnostics.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('STARTUP_LOGIN_ROOT'),
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext,
|
||||
PAGE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupDiagnostics.disposeFirstFrame(PAGE_NAME);
|
||||
StartupDiagnostics.clearStatusUpdatedListener(PAGE_NAME);
|
||||
}
|
||||
|
||||
private openNetworkScenario(): void {
|
||||
this.navigateTo('pages/StartupNetwork');
|
||||
}
|
||||
|
||||
private openPrivacyScenario(): void {
|
||||
this.navigateTo('pages/StartupPrivacy');
|
||||
}
|
||||
|
||||
private completeLogin(): void {
|
||||
if (!AuthSession.setLoggedIn(true)) {
|
||||
this.startupStatus = `保存登录状态失败,请重试。\n${StartupDiagnostics.getStatusText()}`;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.getUIContext().getRouter().replaceUrl({ url: 'pages/Index' }).catch((err: Error) => {
|
||||
this.startupStatus = `进入首页失败:${err.message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
});
|
||||
} catch (err) {
|
||||
const message: string = err instanceof Error ? err.message : JSON.stringify(err);
|
||||
this.startupStatus = `进入首页同步异常:${message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
}
|
||||
}
|
||||
|
||||
private navigateTo(url: string): void {
|
||||
try {
|
||||
this.getUIContext().getRouter().pushUrl({ url: url }).catch((err: Error) => {
|
||||
this.startupStatus = `页面跳转失败:${err.message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
});
|
||||
} catch (err) {
|
||||
const message: string = err instanceof Error ? err.message : JSON.stringify(err);
|
||||
this.startupStatus = `页面跳转同步异常:${message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Scroll() {
|
||||
Column({ space: 16 }) {
|
||||
Text('账号登录')
|
||||
.fontSize(28)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
|
||||
Text('首次绘制会触发启动性能检测;进入其他场景后会再次尝试注册,用于验证单次保护。')
|
||||
.fontSize(14)
|
||||
.fontColor('#666666')
|
||||
|
||||
TextInput({ placeholder: '账号', text: this.account })
|
||||
.width('100%')
|
||||
.height(48)
|
||||
.onChange((value: string) => {
|
||||
this.account = value;
|
||||
})
|
||||
|
||||
TextInput({ placeholder: '密码', text: this.password })
|
||||
.type(InputType.Password)
|
||||
.width('100%')
|
||||
.height(48)
|
||||
.onChange((value: string) => {
|
||||
this.password = value;
|
||||
})
|
||||
|
||||
Button('模拟登录成功')
|
||||
.width('100%')
|
||||
.height(46)
|
||||
.onClick(() => {
|
||||
this.completeLogin();
|
||||
})
|
||||
|
||||
Row({ space: 12 }) {
|
||||
Button('模拟网络异常')
|
||||
.layoutWeight(1)
|
||||
.onClick(() => {
|
||||
this.openNetworkScenario();
|
||||
})
|
||||
|
||||
Button('模拟隐私确认')
|
||||
.layoutWeight(1)
|
||||
.onClick(() => {
|
||||
this.openPrivacyScenario();
|
||||
})
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text('启动检测结果')
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.width('100%')
|
||||
|
||||
Text(this.startupStatus)
|
||||
.fontSize(13)
|
||||
.fontColor('#335F8A')
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.backgroundColor('#EEF6FF')
|
||||
.borderRadius(8)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(24)
|
||||
}
|
||||
.id('STARTUP_LOGIN_ROOT')
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { StartupDiagnostics } from '../diagnostics/StartupDiagnostics';
|
||||
|
||||
const PAGE_NAME: string = 'StartupNetwork';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct StartupNetwork {
|
||||
@State startupStatus: string = StartupDiagnostics.getStatusText();
|
||||
|
||||
aboutToAppear(): void {
|
||||
StartupDiagnostics.setStatusUpdatedListener(PAGE_NAME, () => {
|
||||
this.startupStatus = StartupDiagnostics.getStatusText();
|
||||
});
|
||||
StartupDiagnostics.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('STARTUP_NETWORK_ROOT'),
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext,
|
||||
PAGE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupDiagnostics.disposeFirstFrame(PAGE_NAME);
|
||||
StartupDiagnostics.clearStatusUpdatedListener(PAGE_NAME);
|
||||
}
|
||||
|
||||
private retryNetwork(): void {
|
||||
try {
|
||||
this.getUIContext().getRouter().replaceUrl({ url: 'pages/StartupLogin' }).catch((err: Error) => {
|
||||
this.startupStatus = `页面跳转失败:${err.message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
});
|
||||
} catch (err) {
|
||||
const message: string = err instanceof Error ? err.message : JSON.stringify(err);
|
||||
this.startupStatus = `页面跳转同步异常:${message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 18 }) {
|
||||
Text('网络连接异常')
|
||||
.fontSize(26)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
|
||||
Text('这是启动过程中的网络异常模拟页。点击重试后进入登录页,后续页面不应再次提交绘制完成事件。')
|
||||
.fontSize(15)
|
||||
.fontColor('#666666')
|
||||
.textAlign(TextAlign.Center)
|
||||
|
||||
Button('网络已恢复,重试')
|
||||
.width('100%')
|
||||
.height(46)
|
||||
.onClick(() => {
|
||||
this.retryNetwork();
|
||||
})
|
||||
|
||||
Text(this.startupStatus)
|
||||
.fontSize(13)
|
||||
.fontColor('#8A4B08')
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.backgroundColor('#FFF7E8')
|
||||
.borderRadius(8)
|
||||
}
|
||||
.id('STARTUP_NETWORK_ROOT')
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { StartupDiagnostics } from '../diagnostics/StartupDiagnostics';
|
||||
|
||||
const PAGE_NAME: string = 'StartupPrivacy';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct StartupPrivacy {
|
||||
@State startupStatus: string = StartupDiagnostics.getStatusText();
|
||||
|
||||
aboutToAppear(): void {
|
||||
StartupDiagnostics.setStatusUpdatedListener(PAGE_NAME, () => {
|
||||
this.startupStatus = StartupDiagnostics.getStatusText();
|
||||
});
|
||||
StartupDiagnostics.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('STARTUP_PRIVACY_ROOT'),
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext,
|
||||
PAGE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupDiagnostics.disposeFirstFrame(PAGE_NAME);
|
||||
StartupDiagnostics.clearStatusUpdatedListener(PAGE_NAME);
|
||||
}
|
||||
|
||||
private acceptPrivacy(): void {
|
||||
this.navigateTo('pages/StartupLogin');
|
||||
}
|
||||
|
||||
private showNetworkError(): void {
|
||||
this.navigateTo('pages/StartupNetwork');
|
||||
}
|
||||
|
||||
private navigateTo(url: string): void {
|
||||
try {
|
||||
this.getUIContext().getRouter().replaceUrl({ url: url }).catch((err: Error) => {
|
||||
this.startupStatus = `页面跳转失败:${err.message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
});
|
||||
} catch (err) {
|
||||
const message: string = err instanceof Error ? err.message : JSON.stringify(err);
|
||||
this.startupStatus = `页面跳转同步异常:${message}\n${StartupDiagnostics.getStatusText()}`;
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 18 }) {
|
||||
Text('隐私政策确认')
|
||||
.fontSize(26)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
|
||||
Text('这是启动过程中的隐私确认模拟页。页面跳转后,捕获入口和绘制完成时间不应被后续登录页覆盖。')
|
||||
.fontSize(15)
|
||||
.fontColor('#666666')
|
||||
|
||||
Button('同意并进入登录页')
|
||||
.width('100%')
|
||||
.height(46)
|
||||
.onClick(() => {
|
||||
this.acceptPrivacy();
|
||||
})
|
||||
|
||||
Button('模拟网络不可用')
|
||||
.width('100%')
|
||||
.height(46)
|
||||
.onClick(() => {
|
||||
this.showNetworkError();
|
||||
})
|
||||
|
||||
Text(this.startupStatus)
|
||||
.fontSize(13)
|
||||
.fontColor('#3F5D3F')
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.backgroundColor('#EFF8EF')
|
||||
.borderRadius(8)
|
||||
}
|
||||
.id('STARTUP_PRIVACY_ROOT')
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { preferences } from '@kit.ArkData';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
|
||||
export class AuthSession {
|
||||
private static readonly STORE_NAME: string = 'auth_session';
|
||||
private static readonly LOGGED_IN_KEY: string = 'is_logged_in';
|
||||
private static readonly LOG_DOMAIN: number = 0xD003;
|
||||
private static store: preferences.Preferences | undefined = undefined;
|
||||
|
||||
static initialize(context: common.UIAbilityContext): void {
|
||||
try {
|
||||
const options: preferences.Options = { name: AuthSession.STORE_NAME };
|
||||
AuthSession.store = preferences.getPreferencesSync(context, options);
|
||||
} catch (err) {
|
||||
AuthSession.logFailure('初始化登录状态失败。', err);
|
||||
}
|
||||
}
|
||||
|
||||
static isLoggedIn(): boolean {
|
||||
const store: preferences.Preferences | undefined = AuthSession.store;
|
||||
if (store === undefined) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return store.getSync(AuthSession.LOGGED_IN_KEY, false) as boolean;
|
||||
} catch (err) {
|
||||
AuthSession.logFailure('读取登录状态失败。', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static setLoggedIn(loggedIn: boolean): boolean {
|
||||
const store: preferences.Preferences | undefined = AuthSession.store;
|
||||
if (store === undefined) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
store.putSync(AuthSession.LOGGED_IN_KEY, loggedIn);
|
||||
store.flushSync();
|
||||
return true;
|
||||
} catch (err) {
|
||||
AuthSession.logFailure('保存登录状态失败。', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static logFailure(message: string, err: Object): void {
|
||||
const detail: string = err instanceof Error ? err.message : 'Unknown error';
|
||||
hilog.error(AuthSession.LOG_DOMAIN, 'AuthSession', '%{public}s %{public}s', message, detail);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"src": [
|
||||
"pages/StartupLogin",
|
||||
"pages/StartupNetwork",
|
||||
"pages/StartupPrivacy",
|
||||
"pages/Index",
|
||||
"pages/CrashHistory"
|
||||
]
|
||||
|
||||
@@ -44,6 +44,7 @@ import { ConnectionInitiator } from '../communication/ConnectionInitiator';
|
||||
import { APMHelper } from '../monitor/APMHelper';
|
||||
import { cbasService } from '../register/cbasregister/CbasServiceRegister';
|
||||
import { eventSubscription } from '../monitor/AppEvent';
|
||||
import { StartupMonitor } from '../monitor/StartupMonitor';
|
||||
import { StockGroupConstants } from 'biz_selfcode';
|
||||
import { StockGroupHandler } from '../groupsub/business/StockGroupHandler';
|
||||
import { VarietyGroupHandler } from '../groupsub/business/VarietyGroupHandler';
|
||||
@@ -140,6 +141,9 @@ export default class EntryAbility extends UIAbility {
|
||||
hilog.info(0x0000, this.tag, 'Custom Figures registered successfully');
|
||||
// =====================================
|
||||
|
||||
// 初始化启动性能监控(必须在 early onCreate 注册,确保 reportDrawnCompleted 前 watcher 已就绪)
|
||||
StartupMonitor.init();
|
||||
|
||||
DebugToolUtil.TARGET_NAME = BuildProfile.TARGET_NAME
|
||||
DebugToolUtil.PRODUCT_NAME = BuildProfile.PRODUCT_NAME
|
||||
DebugToolUtil.BUILD_MODE_NAME = BuildProfile.BUILD_MODE_NAME
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent';
|
||||
import { HXLog } from 'biz_common/src/main/ets/logger/HXLog';
|
||||
import { HXUserService } from 'biz_hxservice';
|
||||
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
|
||||
import { AppConfigManager } from 'biz_common';
|
||||
import { inspector } from '@kit.ArkUI';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
|
||||
const TAG: string = 'StartupMonitor';
|
||||
|
||||
/**
|
||||
* 启动性能监控
|
||||
*
|
||||
* 订阅系统 APP_LAUNCH 事件,获取系统计算的冷启动耗时(extend_time),
|
||||
* 并通过现有的 ElkService 上传结构化指标。与 AppEvent.ets 的故障事件监听相互独立。
|
||||
*
|
||||
* 指标定义(见 HarmonyOS 启动性能监控接入方案_20260805.md):
|
||||
* - module: launch
|
||||
* - metric: cold_start_time(ms)
|
||||
* - dimensions: start_type / entry_page / app_version
|
||||
*/
|
||||
export class StartupMonitor {
|
||||
private static readonly MAX_EVENT_KEYS: number = 20;
|
||||
|
||||
/**
|
||||
* 首帧绘制完成时快照的入口页面名称,在 onDraw 回调中赋值。
|
||||
* 与 APP_LAUNCH 事件处理处在同一同步时序内,避免页面跳转覆盖。
|
||||
*/
|
||||
private static capturedEntryPage: string = 'unknown';
|
||||
|
||||
private static watcherInitialized: boolean = false;
|
||||
private static drawReportSubmitted: boolean = false;
|
||||
private static activeObserverOwner: string = '';
|
||||
private static activeDisposer?: () => void;
|
||||
private static processedEventKeys: Array<string> = [];
|
||||
|
||||
/**
|
||||
* 在入口页面注册 HOME_ROOT 首次绘制监听,自动完成:
|
||||
* 1. 创建 observer
|
||||
* 2. 监听 draw → 仅触发一次 → 快照 entryPage → 调用 reportDrawnCompleted
|
||||
*
|
||||
* 页面在 aboutToDisappear 中调用 disposeFirstFrame(pageName) 清理即可,
|
||||
* 无需在页面侧持有清理函数。
|
||||
*/
|
||||
static reportFirstFrameOnDraw(
|
||||
createObserver: () => inspector.ComponentObserver,
|
||||
abilityContext: common.UIAbilityContext,
|
||||
pageName: string
|
||||
): void {
|
||||
if (StartupMonitor.drawReportSubmitted) {
|
||||
HXLog.i(TAG, `Skip duplicate draw registration for page: ${pageName}`)
|
||||
return
|
||||
}
|
||||
|
||||
StartupMonitor.disposeFirstFrame(StartupMonitor.activeObserverOwner)
|
||||
|
||||
try {
|
||||
const observer = createObserver();
|
||||
const onDraw = () => {
|
||||
observer.off('draw', onDraw);
|
||||
if (StartupMonitor.activeObserverOwner === pageName) {
|
||||
StartupMonitor.activeObserverOwner = ''
|
||||
StartupMonitor.activeDisposer = undefined
|
||||
}
|
||||
|
||||
// 多个入口页面可能在同一冷启动中依次绘制。必须在调用系统接口前设置全局状态,
|
||||
// 防止后续页面或重复 draw 回调再次提交 reportDrawnCompleted。
|
||||
if (StartupMonitor.drawReportSubmitted) {
|
||||
HXLog.i(TAG, `Skip duplicate draw callback for page: ${pageName}`)
|
||||
return
|
||||
}
|
||||
StartupMonitor.drawReportSubmitted = true
|
||||
StartupMonitor.capturedEntryPage = pageName
|
||||
|
||||
try {
|
||||
abilityContext.reportDrawnCompleted(() => {
|
||||
});
|
||||
HXLog.i(TAG, `reportDrawnCompleted submitted for page: ${pageName}`)
|
||||
} catch (e) {
|
||||
// 同步异常表示系统调用尚未提交,允许后续入口页面重试。
|
||||
StartupMonitor.drawReportSubmitted = false
|
||||
StartupMonitor.capturedEntryPage = 'unknown'
|
||||
HXLog.e(TAG, `reportDrawnCompleted failed synchronously: ${JSON.stringify(e)}`)
|
||||
}
|
||||
};
|
||||
|
||||
StartupMonitor.activeObserverOwner = pageName
|
||||
StartupMonitor.activeDisposer = () => {
|
||||
observer.off('draw', onDraw);
|
||||
};
|
||||
observer.on('draw', onDraw);
|
||||
HXLog.i(TAG, `Draw listener registered for page: ${pageName}`);
|
||||
} catch (e) {
|
||||
HXLog.e(TAG, `Create draw listener failed for page ${pageName}: ${JSON.stringify(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消入口页面的首帧绘制监听。
|
||||
*/
|
||||
static disposeFirstFrame(pageName: string): void {
|
||||
if (pageName === '' || StartupMonitor.activeObserverOwner !== pageName) {
|
||||
return
|
||||
}
|
||||
StartupMonitor.activeDisposer?.();
|
||||
StartupMonitor.activeObserverOwner = ''
|
||||
StartupMonitor.activeDisposer = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 APP_LAUNCH 事件监听。
|
||||
* 应在 EntryAbility.onCreate 中尽早调用,确保在 reportDrawnCompleted 触发事件前完成注册。
|
||||
*/
|
||||
static init(): void {
|
||||
if (StartupMonitor.watcherInitialized) {
|
||||
return
|
||||
}
|
||||
hiAppEvent.addWatcher({
|
||||
name: "startupWatcher",
|
||||
appEventFilters: [{
|
||||
domain: hiAppEvent.domain.OS,
|
||||
names: [hiAppEvent.event.APP_LAUNCH]
|
||||
}],
|
||||
onReceive: async (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
|
||||
HXLog.i(TAG, `APP_LAUNCH onReceive: domain=${domain}`)
|
||||
StartupMonitor.processAppLaunchEvents(appEventGroups)
|
||||
}
|
||||
});
|
||||
StartupMonitor.watcherInitialized = true
|
||||
HXLog.i(TAG, 'StartupMonitor watcher registered')
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历 APP_LAUNCH 事件,仅处理冷启动(start_type === 0)且 extend_time 已填充的记录,
|
||||
* 计算 cold_start_time 并上传。
|
||||
*/
|
||||
private static processAppLaunchEvents(appEventGroups: Array<hiAppEvent.AppEventGroup>): void {
|
||||
try {
|
||||
if (!appEventGroups || appEventGroups.length === 0) {
|
||||
return
|
||||
}
|
||||
appEventGroups.forEach((eventGroup: hiAppEvent.AppEventGroup) => {
|
||||
eventGroup.appEventInfos.forEach((eventInfo: hiAppEvent.AppEventInfo) => {
|
||||
const startType = eventInfo.params['start_type'] as number;
|
||||
// 仅统计冷启动
|
||||
if (startType !== 0) {
|
||||
HXLog.i(TAG, `Skip non-cold-start: start_type=${startType}`)
|
||||
return
|
||||
}
|
||||
const extendTime = eventInfo.params['extend_time'] as number;
|
||||
// extend_time 即为冷启动耗时(ms):从 icon_input_time 到 reportDrawnCompleted 的时间差,
|
||||
// 由系统在 reportDrawnCompleted 调用后填充。未填充说明首帧尚未完成。
|
||||
if (!extendTime || extendTime <= 0) {
|
||||
HXLog.w(TAG, `APP_LAUNCH has no valid extend_time, skip`)
|
||||
return
|
||||
}
|
||||
|
||||
const iconInputTime = eventInfo.params['icon_input_time'] as number;
|
||||
const eventTime = eventInfo.params['time'] as number;
|
||||
const processName = (eventInfo.params['process_name'] as string) ?? '';
|
||||
const eventKey = `${eventTime}|${iconInputTime}|${startType}|${processName}`;
|
||||
if (StartupMonitor.processedEventKeys.indexOf(eventKey) !== -1) {
|
||||
HXLog.i(TAG, `Skip duplicate APP_LAUNCH: ${eventKey}`)
|
||||
return
|
||||
}
|
||||
StartupMonitor.processedEventKeys.push(eventKey)
|
||||
if (StartupMonitor.processedEventKeys.length > StartupMonitor.MAX_EVENT_KEYS) {
|
||||
StartupMonitor.processedEventKeys.shift()
|
||||
}
|
||||
|
||||
HXLog.i(TAG,
|
||||
`cold_start_time=${extendTime}ms, ` +
|
||||
`icon_input_time=${iconInputTime}, ` +
|
||||
`entry_page=${StartupMonitor.capturedEntryPage}`)
|
||||
|
||||
StartupMonitor.uploadColdStartTime(extendTime, startType, eventInfo)
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
HXLog.e(TAG, `Error processing APP_LAUNCH: ${JSON.stringify(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传冷启动耗时指标到 ELK。
|
||||
* 使用 isInstant=true 异步上传,不阻塞页面渲染。
|
||||
*/
|
||||
private static uploadColdStartTime(
|
||||
coldStartTime: number,
|
||||
startType: number,
|
||||
eventInfo: hiAppEvent.AppEventInfo
|
||||
): void {
|
||||
try {
|
||||
const bundleVersion = (eventInfo.params['bundle_version'] as string) ?? '';
|
||||
const animationFinishTime = eventInfo.params['animation_finish_time'] as number;
|
||||
|
||||
const metricPayload: Record<string, Object> = {
|
||||
// 核心指标
|
||||
cold_start_time: coldStartTime,
|
||||
// 维度
|
||||
start_type: startType,
|
||||
entry_page: StartupMonitor.capturedEntryPage,
|
||||
app_version: StartupMonitor.getAppVersion(),
|
||||
bundle_version: bundleVersion,
|
||||
// 原始系统字段,用于后端校验和去重
|
||||
event_time: eventInfo.params['time'],
|
||||
icon_input_time: eventInfo.params['icon_input_time'],
|
||||
process_name: eventInfo.params['process_name'],
|
||||
extend_time: eventInfo.params['extend_time'],
|
||||
animation_finish_time: animationFinishTime,
|
||||
}
|
||||
|
||||
const builder = new StartupElkBuilder('i')
|
||||
builder.userid(HXUserService.getInstance().getUserId())
|
||||
builder.messageKey('cold_start_time')
|
||||
builder.messageValue(JSON.stringify(metricPayload))
|
||||
|
||||
const message = new ElkUploadMessage(builder)
|
||||
// 异步上传,避免阻塞启动流程
|
||||
ElkService.getInstance().pushBusinessLog(message, true)
|
||||
HXLog.i(TAG, `Cold start metric uploaded: ${coldStartTime}ms`)
|
||||
} catch (e) {
|
||||
HXLog.e(TAG, `uploadColdStartTime failed: ${JSON.stringify(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private static getAppVersion(): string {
|
||||
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull() ?? 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ELK 消息构造器,业务来源标记为 StartupMonitor
|
||||
*/
|
||||
class StartupElkBuilder extends ElkUploadMessageBuilder {
|
||||
constructor(biz_level: string = 'i') {
|
||||
super(Date.now(), 'StartupMonitor', biz_level);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { HXNetwork } from './HXNetwork'
|
||||
import { ConnectionSeq } from '@kernel/lib_communication'
|
||||
import { AuthProviderService } from 'biz_auth'
|
||||
import { AdvertisingMonitorService } from 'biz_hxservice'
|
||||
import { StartupMonitor } from '../monitor/StartupMonitor'
|
||||
|
||||
const SettingWant: Want = {
|
||||
bundleName: 'com.huawei.hmos.settings',
|
||||
@@ -24,6 +25,7 @@ struct NetworkAnomalyPage {
|
||||
private TAG: string = "HXNetwork";
|
||||
private systemBarColor = "#00000000";
|
||||
private systemBarContentColor = "#FFFFFFFF";
|
||||
context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
||||
|
||||
async setSystemBar() {
|
||||
let context = (this.getUIContext().getHostContext() as common.UIAbilityContext);
|
||||
@@ -47,6 +49,13 @@ struct NetworkAnomalyPage {
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.setSystemBar()
|
||||
StartupMonitor.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
|
||||
this.context, 'NetworkAnomalyPage');
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupMonitor.disposeFirstFrame('NetworkAnomalyPage')
|
||||
}
|
||||
|
||||
onExit() {
|
||||
@@ -118,6 +127,8 @@ struct NetworkAnomalyPage {
|
||||
},
|
||||
'middle': { 'anchor': '__container__', 'align': HorizontalAlign.Center },
|
||||
})
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
.id('HOME_ROOT')
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import { enableDarkMode, ThemeManagerEmitterConstants } from '@kernel/theme_mana
|
||||
import { CmeTraceForeignManager } from 'biz_quote/src/main/ets/manager/CmeTraceForeignManager';
|
||||
import { HQUGCManager } from 'biz_market';
|
||||
import { AdsManager } from '@b2c-f/fuhm-ads';
|
||||
import { StartupMonitor } from '../monitor/StartupMonitor';
|
||||
|
||||
|
||||
const TAG: string = 'Struct Index';
|
||||
@@ -309,6 +310,8 @@ struct IndexM {
|
||||
|
||||
aboutToDisappear() {
|
||||
HXLog.d(TAG, 'Index aboutToDisappear triggered at ' + Date.now())
|
||||
// 页面退出时取消首帧绘制监听
|
||||
StartupMonitor.disposeFirstFrame('Index');
|
||||
emitter.off(EmitterConstants.SWIPER_CHANGETOINDEX_1_TEMP)
|
||||
emitter.off(EmitterConstants.TAB_UI_MANAGER_CHANGE_INDEX)
|
||||
emitter.off(QuoteSettingEvents.SETTING_UPDATE, this.onSettingUpdateCallback)
|
||||
@@ -361,6 +364,11 @@ struct IndexM {
|
||||
|
||||
// 冷启动先初始化一次 drawline 基础能力,保证首次进入行情页即可绘制同步线。
|
||||
DrawLineInit.init()
|
||||
|
||||
// 注册首帧绘制监听,绘制完成时自动调用 reportDrawnCompleted
|
||||
StartupMonitor.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
|
||||
this.context, 'Index');
|
||||
}
|
||||
|
||||
onPageShow() {
|
||||
@@ -522,6 +530,7 @@ struct IndexM {
|
||||
Blank().height(px2vp(GlobalContext.navigationBarHeight)).flexShrink(0).backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
}
|
||||
}
|
||||
.id('HOME_ROOT')
|
||||
.height('100%')
|
||||
.width('100%')
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { window } from '@kit.ArkUI'
|
||||
import { ConnectionSeq } from '@kernel/lib_communication/src/main/ets/data/CommunicationConstants'
|
||||
import { deviceInfo } from '@kit.BasicServicesKit'
|
||||
import { AdvertisingMonitorService } from 'biz_hxservice'
|
||||
import { StartupMonitor } from '../monitor/StartupMonitor'
|
||||
|
||||
/**
|
||||
* 启动页面
|
||||
@@ -46,6 +47,13 @@ struct LauncherPage {
|
||||
aboutToAppear(): void {
|
||||
this.setSystemBar()
|
||||
this.userPrivacyDialogVisible = !PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false);
|
||||
StartupMonitor.reportFirstFrameOnDraw(
|
||||
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
|
||||
this.context, 'LauncherPage');
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
StartupMonitor.disposeFirstFrame('LauncherPage')
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
@@ -102,6 +110,8 @@ struct LauncherPage {
|
||||
},
|
||||
'middle': { 'anchor': '__container__', 'align': HorizontalAlign.Center },
|
||||
}).id("image")
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
.id('HOME_ROOT')
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user