白屏两秒,用户以为你卡死了
你有没有遇到过这种情况:点开应用,先白屏一两秒,然后才出现首页。用户不知道发生了什么,只觉得“这 App 好卡”。更糟的是,有些应用启动时还要转圈,转完才显示内容。
冷启动慢,原因通常不是代码写得烂,而是启动阶段做了太多不该做的事。网络请求、数据库初始化、第三方 SDK 注册、大量同步 IO,全堆在onWindowStageCreate里,能不慢吗?
这篇就讲清楚 HarmonyOS 7 的冷启动流程,怎么定位耗时点,怎么用 AppStartup 做任务编排,以及那些你天天在犯的启动错误。
https://placeholder-01.jpg/
一、冷启动到底经历了什么
HarmonyOS 应用的冷启动大致分几个阶段:
应用进程创建:系统 fork 出应用进程。
AbilityStage 创建:触发
onCreate。UIAbility 创建:触发
onCreate。WindowStage 创建:触发
onWindowStageCreate,加载首页。首页渲染:ArkUI 构建组件树,上屏。
你写的业务代码,主要集中在 2、3、4 三个阶段。如果这些阶段里有耗时操作,启动就会变慢。
先看一个典型的错误写法:
typescript
export default class EntryAbility extends UIAbility { onCreate(want, launchParam) { // 错误:在 onCreate 里做网络请求 fetchUserInfo(); // 错误:同步读文件 const config = fs.readTextSync('/data/config.json'); // 错误:初始化第三方 SDK initAnalytics(); } onWindowStageCreate(windowStage) { windowStage.loadContent('pages/Index', (err) => { if (err.code) { console.error('加载首页失败'); } }); } }这些操作每一个都可能花几十到几百毫秒,加起来启动不慢才怪。
二、用埋点找出真正的耗时点
优化之前,先别猜。用hiTraceMeter或者简单的Date.now()打点,看看每个阶段花了多久。
typescript
import hiTraceMeter from '@ohos.hiTraceMeter'; export default class EntryAbility extends UIAbility { onCreate(want, launchParam) { hiTraceMeter.startTrace('ability_onCreate', 1); // 业务逻辑 hiTraceMeter.finishTrace('ability_onCreate', 1); } onWindowStageCreate(windowStage) { hiTraceMeter.startTrace('windowStage_create', 1); windowStage.loadContent('pages/Index', (err) => { hiTraceMeter.finishTrace('windowStage_create', 1); }); } }跑几次,在 DevEco 的 Profiler 里看时间线。你会发现,真正耗时的往往不是框架本身,而是你自己加的初始化逻辑。
https://placeholder-02.png/
三、AppStartup:把启动任务管起来
HarmonyOS 提供了 AppStartup 机制,可以把启动任务拆成一个个 StartupTask,按依赖关系自动编排,还能控制是否在启动阶段执行。
先定义一个启动任务:
typescript
import { StartupTask, StartupTaskContext } from '@ohos.app.appstartup'; export default class InitAnalyticsTask extends StartupTask { async init(context: StartupTaskContext): Promise<void> { // 初始化埋点 SDK await initAnalytics(); } }然后在module.json5里配置:
json
{ "module": { "appStartup": { "startupTasks": [ { "name": "InitAnalyticsTask", "srcEntry": "./ets/startup/InitAnalyticsTask.ets" } ] } } }AppStartup 的好处是:任务可以并行、可以设置依赖、可以延迟执行。比如你把非首屏必需的初始化都标记为isOnDemand: true,等首页显示完再执行。
typescript
export default class InitAnalyticsTask extends StartupTask { isOnDemand: boolean = true; // 按需执行 async init(context: StartupTaskContext): Promise<void> { await initAnalytics(); } }这样启动阶段只做最关键的事,其他统统往后放。
四、首页渲染优化:别在 build 里做重活
首页本身也要注意。很多人喜欢在aboutToAppear里发网络请求,请求回来之前页面是空的,用户看到的就是白屏。
更好的做法是:先渲染骨架屏,数据到了再替换。
typescript
@Entry @Component struct Index { @State data: string[] = []; @State isLoading: boolean = true; aboutToAppear() { this.loadData(); } async loadData() { this.isLoading = true; try { const result = await fetchData(); this.data = result; } finally { this.isLoading = false; } } build() { Column() { if (this.isLoading) { SkeletonList() // 骨架屏组件 } else { List() { ForEach(this.data, (item: string) => { ListItem() { Text(item) } }) } } } } }骨架屏不用等数据,立刻就能渲染,用户感知上快很多。等数据回来再替换,体验会好很多。
另外,build()里别做复杂计算。ArkUI 的 build 会频繁执行,你在里面写个循环排序,页面就卡了。所有计算提前在aboutToAppear或onPageShow里做完。
https://placeholder-03.png/
五、那些年我们踩过的启动坑
坑一:在onCreate里同步读文件。
fs.readTextSync是同步的,文件稍微大一点就阻塞主线程。改成异步:
typescript
const config = await fs.readText('/data/config.json');坑二:启动时注册大量监听器。
比如网络状态监听、位置监听、传感器监听,这些注册本身不慢,但回调可能频繁触发,影响启动。非必要的监听,等首页显示完再注册。
坑三:第三方 SDK 初始化太重。
很多 SDK 的init方法内部会做网络请求、读文件、开线程。如果它不是首屏必需的,用 AppStartup 的isOnDemand延迟加载。
坑四:console.log打太多。
开发阶段随便打,生产环境一定要关掉。大量日志输出会拖慢启动,尤其是循环里打日志。
https://placeholder-04.jpg/
六、总结
这篇讲了 HarmonyOS 7 冷启动的流程、定位方法和优化手段。核心记住三点:
启动阶段只做最关键的事,其他交给 AppStartup 延迟执行。
首页先渲染骨架屏,别让用户干等白屏。
用埋点找耗时点,别凭感觉优化。