系列文章《鸿蒙开发从入门到专家》第 51 篇 · 高级
第 8 篇讲性能优化时提到过启动速度,但那篇侧重的是"量化和定位"。这篇往深走一层:当你知道启动慢,具体怎么改?
HarmonyOS NEXT 从 API 12 开始提供了 AppStartup 启动框架,允许你用声明式的方式编排初始化任务,自动处理依赖关系,支持异步并行执行。用好了,冷启动能从 2 秒压到 800ms 以内。用不好,就是另一层"配置地狱"。
一、冷启动慢在哪
先搞清楚问题再动手。鸿蒙应用的冷启动链路:
用户点击图标
→ AbilityStage onCreate(进程创建)
→ UIAbility onCreate(Ability 初始化)
→ 页面首帧渲染(ArkUI build)
→ 页面可交互
大部分应用的初始化代码堆在 UIAbility.onCreate 里,串行执行:初始化日志 → 初始化网络 → 初始化数据库 → 初始化推送 → 初始化埋点 → 拉取用户信息 → 加载首屏数据。每一步 50-200ms,叠起来就是 1-2 秒。
问题在于:
很多初始化任务之间没有依赖关系,却在串行等待。
有些任务(如推送、埋点)不阻塞首屏渲染,却被放在了首屏渲染之前。
有些任务有依赖关系(如网络初始化依赖日志),但代码顺序写反了导致出错。
AppStartup 就是来解决这个编排问题的。
二、AppStartup 基本用法
2.1 开启启动框架
在 module.json5 中配置:
{
"module": {
"name": "entry",
"type": "entry",
"appStartup": "$profile:startup_config",
"abilities": [...]
}
}
然后在 resources/base/profile/ 下创建 startup_config.json:
{
"startupTasks": [
{
"name": "StartupTask_Logger",
"srcEntry": "./ets/startup/LoggerTask.ets",
"runOnThread": "mainThread"
},
{
"name": "StartupTask_Network",
"srcEntry": "./ets/startup/NetworkTask.ets",
"runOnThread": "taskPool",
"dependencies": ["StartupTask_Logger"]
},
{
"name": "StartupTask_Database",
"srcEntry": "./ets/startup/DatabaseTask.ets",
"runOnThread": "taskPool",
"dependencies": ["StartupTask_Logger"]
},
{
"name": "StartupTask_UserInfo",
"srcEntry": "./ets/startup/UserInfoTask.ets",
"runOnThread": "taskPool",
"dependencies": ["StartupTask_Network", "StartupTask_Database"]
},
{
"name": "StartupTask_Analytics",
"srcEntry": "./ets/startup/AnalyticsTask.ets",
"runOnThread": "taskPool"
}
]
}
这个配置定义了 5 个启动任务,它们的依赖关系:
Logger(主线程)
├── Network(TaskPool,依赖 Logger)
└── Database(TaskPool,依赖 Logger)
└── UserInfo(TaskPool,依赖 Network + Database)
Analytics(TaskPool,无依赖)
框架会自动做拓扑排序,Logger 先执行,Network 和 Database 并行执行,UserInfo 等两者都完成后执行。Analytics 没有依赖,和 Logger 并行启动。
2.2 实现启动任务
每个任务继承 StartupTask,用 @Sendable 装饰器标注:
// startup/LoggerTask.ets
import { StartupTask, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Sendable
export default class LoggerTask extends StartupTask {
constructor() {
super();
}
async init(context: common.AbilityStageContext): Promise<string> {
// 初始化日志模块
hilog.info(0x0000, 'AppStartup', 'LoggerTask init');
// 模拟初始化耗时
await new Promise<void>(resolve => setTimeout(resolve, 30));
return 'LoggerTask done';
}
onDependencyCompleted(dependency: string, result: Object): void {
hilog.info(0x0000, 'AppStartup',
`LoggerTask dependency: %{public}s, result: %{public}s`,
dependency, JSON.stringify(result));
}
}
// startup/NetworkTask.ets
import { StartupTask, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Sendable
export default class NetworkTask extends StartupTask {
constructor() {
super();
}
async init(context: common.AbilityStageContext): Promise<string> {
hilog.info(0x0000, 'AppStartup', 'NetworkTask init');
// 初始化 HTTP 客户端、配置拦截器等
await new Promise<void>(resolve => setTimeout(resolve, 150));
return 'NetworkTask done';
}
onDependencyCompleted(dependency: string, result: Object): void {
hilog.info(0x0000, 'AppStartup',
`NetworkTask sees dependency %{public}s completed`, dependency);
}
}
2.3 手动触发启动
配置好之后,有两种触发方式。
方式一:框架自动触发。只要在 module.json5 配了 appStartup,AbilityStage 创建时框架会自动读取配置文件并执行。
方式二:手动触发。在 UIAbility.onCreate 中手动调用:
import { AbilityConstant, UIAbility, Want, startupManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'AppStartup', 'Ability onCreate');
// 只执行阻塞首屏的任务
let criticalTasks = ['StartupTask_Logger', 'StartupTask_Network'];
startupManager.run(criticalTasks).then(() => {
hilog.info(0x0000, 'AppStartup', 'Critical tasks done');
}).catch((error: BusinessError) => {
hilog.error(0x0000, 'AppStartup',
'run failed, code: %{public}d, msg: %{public}s',
error.code, error.message);
});
}
}
手动触发的好处是你可以选择只运行关键路径上的任务,把非关键任务延迟到首屏渲染后。
三、任务分级策略
不是所有初始化任务都要在启动时跑完。根据对首屏的影响,分三级:
代码实现:
// AbilityStage - P0 任务由框架自动执行
import { AbilityStage } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { startupManager } from '@kit.AbilityKit';
export default class MyApp extends AbilityStage {
onCreate(): void {
hilog.info(0x0000, 'AppStartup', 'AbilityStage onCreate');
// P0 任务由 AppStartup 框架自动执行
// 在 startup_config.json 中配置的会在此时自动跑
}
}
// UIAbility - P1/P2 任务延迟执行
export default class EntryAbility extends UIAbility {
// P1 任务:首屏渲染后
runP1Tasks(): void {
startupManager.run(['StartupTask_UserInfo']).then(() => {
hilog.info(0x0000, 'AppStartup', 'P1 tasks done');
});
}
// P2 任务:页面可见后延迟 2 秒
runP2Tasks(): void {
setTimeout(() => {
startupManager.run(['StartupTask_Analytics']).then(() => {
hilog.info(0x0000, 'AppStartup', 'P2 tasks done');
});
}, 2000);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', () => {
// 首屏加载完成后触发 P1
this.runP1Tasks();
// P2 在 P1 之后
this.runP2Tasks();
});
}
}
四、配置超时与监听
AppStartup 支持设置超时时间和监听回调。在 startup_config.json 的同级目录下创建参数文件,或者在 startup_config.json 中直接配置:
{
"configEntry": "$profile:startup_params"
}
startup_params.json:
{
"timeout": 10000,
"configEntry": "$profile:startup_config"
}
代码层面监听任务执行:
import { StartupListener, StartupConfig } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
let onCompletedCallback = (error: BusinessError) => {
if (error) {
hilog.error(0x0000, 'AppStartup',
'Startup failed: %{public}s', JSON.stringify(error));
} else {
hilog.info(0x0000, 'AppStartup', 'All startup tasks completed');
}
};
let config: StartupConfig = {
timeout: 10000,
listener: {
onCompleted: onCompletedCallback
} as StartupListener
};
startupManager.run(['StartupTask_Logger', 'StartupTask_Network'], config)
.then(() => {
hilog.info(0x0000, 'AppStartup', 'Run completed');
});
}
}
超时机制很重要:某个第三方 SDK 初始化卡住时,不至于把整个应用启动链路堵死。
五、性能对比
优化前(串行初始化)和优化后(AppStartup 并行)的对比:
并行省了约 180ms。如果任务更多(大项目可能有 15-20 个初始化任务),收益更大。
六、踩坑记录
坑 1:@Sendable 限制。 StartupTask 类用 @Sendable 装饰,意味着它的字段不能引用非 Sendable 对象。如果初始化逻辑需要访问全局单例,要通过 init 的 context 参数获取,不能直接在类字段里持有。
坑 2:循环依赖。 启动框架会检测循环依赖并报错(错误码 28800002)。如果 A 依赖 B、B 又依赖 A,框架直接拒绝启动。配置时要画出依赖图确认无环。
坑 3:TaskPool 限制。 runOnThread 设为 taskPool 时,任务在 TaskPool 线程执行。注意 TaskPool 的线程数量和并发上限,如果同时并行 10 个任务,实际并发度受 TaskPool 配置限制。
坑 4:只在 entry 模块可用。 启动框架只支持在 entry 类型的 HAP 中使用。feature 模块的初始化逻辑需要通过依赖注入或事件总线触发,不能走 AppStartup。
小结
AppStartup 解决的不是"初始化代码怎么写"的问题,而是"初始化任务怎么编排"的问题。核心收益是并行执行和依赖管理。配合任务分级策略(P0 阻塞、P1 延迟、P2 空闲),可以把冷启动时间从"所有任务串行"降到"关键路径串行 + 非关键并行延迟"。
下篇讲列表渲染。LazyForEach 是鸿蒙长列表性能的关键,但很多人用了还是卡。我们来看为什么。
参考阅读:
第 8 篇:鸿蒙性能优化实战(启动速度部分)
第 31 篇:鸿蒙性能剖析:Profiler 工具链与卡顿定位
官方文档:@ohos.app.appstartup.StartupTask