feat: 完善启动性能监控流程

- 增加登录、网络异常和隐私确认启动场景
- 持久化登录状态并支持首页退出登录
- 保证每个进程只提交一次 reportDrawnCompleted
- 按启动事件唯一键去重 APP_LAUNCH 上报
- 同步优化逻辑到 future_entry_demo 并在 CrashDiagnosticsDemo 真机验证
This commit is contained in:
clz
2026-08-05 16:19:08 +08:00
parent 56ad003e04
commit d8513d1f1d
13 changed files with 956 additions and 5 deletions
@@ -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"
]