往期推文全新看点
- 鸿蒙(HarmonyOS)北向开发知识点记录~
- 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
- 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
- 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
- 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
- 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
- 记录一场鸿蒙开发岗位面试经历~
- 持续更新中……
概述
应用启动时延是影响用户体验的关键要素,是指从用户点击桌面应用图标、通知或其他入口启动应用,到应用界面内容成功加载并显示在屏幕上的时间间隔。如果这段时间耗时比较长,肯定会影响用户的体验。
应用启动可以分为冷启动和热启动,当应用启动时,后台没有该应用的进程,这时系统会重新创建应用的进程, 这种启动方式就叫做冷启动;而热启动是当应用程序已经在后台运行,用户再次打开应用程序时,应用程序仍然在内存中,可以直接从内存中加载并继续之前的状态,而不需要重新初始化和加载资源。
当应用冷启动时延大于1100ms时,可以认为是应用启动缓慢。
本文将介绍以下内容,来帮助开发者提升应用的冷启动速度,避免卡顿感:
- 应用冷启动流程
- 识别启动缓慢问题
- 提升应用冷启动速度
应用冷启动流程
在优化应用冷启动体验前,需要先了解应用冷启动的流程和几个重要的生命周期。应用冷启动的过程大致可分成以下5个阶段:应用进程创建&初始化、Application&Ability初始化、Ability/AbilityStage生命周期、加载绘制首页,如下图所示。
- 应用进程创建&初始化阶段:该阶段主要是系统完成应用进程的创建以及初始化的过程,包含了启动页图标(startWindowIcon)的解码。
- Application&Ability初始化:该阶段主要是资源加载、虚拟机创建、Application&Ability相关对象的创建与初始化、依赖模块的加载等。
- Ability/AbilityStage生命周期:该阶段主要是AbilityStage/Ability的启动生命周期,执行相应的生命周期回调。
- 加载绘制首页:该阶段主要是加载首页内容、测量布局、刷新组件并绘制。
- 网络数据二次刷新:该阶段主要是应用根据业务需要对网络数据进行请求、处理、二次刷新。
可见如果想要提升应用冷启动速度,需要缩短以上几个阶段的耗时。
识别启动缓慢问题
如果开发者需要分析启动过程的耗时瓶颈,优化应用或服务的冷启动速度,可使用Profiler提供的Launch场景分析能力,录制启动过程中的关键数据进行分析,从而识别出导致启动缓慢的原因所在。Profiler Launch可以拆解应用冷启动过程,抓取不同阶段的耗时数据,帮助开发者快速分析冷启动过程的耗时瓶颈。
从上图可以看到Launch将应用的冷启动过程拆解为以下几个阶段:
- Create Process:应用进程创建阶段,对应的trace打点为AbilityManagerService::StartAbilityMissionListManager::StartAbilityLocked##{bundleName}
- Application Launching:应用启动阶段,对应的trace打点为AppMgrServiceInner::AttachApplication##{bundleName}
- UI Ability Launching:UIAbility启动阶段,对应的trace打点为MainThread::HandleLaunchAbility##{bundleName}
- UI Ability OnForeground:应用进入前台阶段,对应的trace打点为AbilityThread::HandleAbilityTransaction
- First Frame - App Phase:App首帧渲染提交阶段,对应的trace打点为H:ReceiveVsync,H:MarshRSTransactionData
- First Frame - Render Phase:RS首帧渲染提交阶段,对应的trace打点为H:ReceiveVsync,H:RSMainThread::ProcessCommandUn
- EntryAbility:应用启动之后的阶段,渲染完成,首页显示
从Create Process阶段开始到First Frame - Render Phase阶段,这段时间可以看作是应用的冷启动时间,在Launch泳道到上,使用鼠标左键拖动这两个阶段的区间,可以看到应用冷启动时间为215.2ms,这个启动速度是比较快的。
冷启动缓慢示例分析
运行如下示例代码,我们可以明显的感知应用启动比较缓慢。接下来我们通过这个示例,结合Launch来分析应用冷启动缓慢问题。
const LARGE_NUMBER = 200000000;
const DELAYED_TIME = 1000;
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
aboutToAppear(): void {
console.log('aboutToAppear');
this.computeTask();
}
computeTask(): void {
let count = 0;
while (count < LARGE_NUMBER) {
count++;
}
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
首先是创建Launch分析录制,可以看到整个的启动时间比较长,其中UI Ability OnForeground这个阶段占据应用冷启动过程的大部分时间,耗时达到了4.1s,所以我们需要重点分析这个阶段的耗时。
针对应用冷启动问题的性能分析,有以下两种方式可以选择,一种是分析主线程的Trace数据,另一种则是分析采样得到的函数热点。
分析主线程的Trace数据
- 单击“Launch”泳道上的UI Ability OnForeground阶段,在下方的“Details”详情面板中,可查看到所选阶段的耗时统计情况;
- 展开UI Ability OnForeground统计信息折叠表,可以看到各函数的具体耗时信息;
- 根据Duration找到耗时最长的函数aboutToAppear;
- 单击图标按钮,可直接跳转至主线程的打点任务中,查看相关Trace数据,如下图所示。
可以看到在UI Ability OnForeground阶段的耗时基本是由aboutToAppear造成的,我们再看aboutToAppear中的代码逻辑,可以推断是由于计算任务computeTask耗时造成的。
分析采样得到的函数热点
我们也可以分析采样得到的函数热点直观的显示应用冷启动过程中具体函数的耗时,如下图:
- 单击“Launch”泳道上的UI Ability OnForeground阶段;
- 选择“ArkTS Callstack”泳道,其会基于时间轴展示CPU使用率和状态的变化,以及当前调用栈名称和调用类型;
- 下方“Details”详情面板中查看到这段时间内的函数热点,其会以Top-Down形式的树状列表进行展示。很明显aboutToAppear函数中的;computeTask函数耗时最多,占整个阶段的96.7%,双击该函数可以跳转到源码。
- 此外,点击底部Flame Chart按钮打开火焰图可以更直观的看出热点函数的耗时情况,如下图所示。
冷启动速度优化
通过前面的分析,冷启动缓慢是由于在aboutToAppear执行了耗时计算任务,我们可以将该computeTask以异步延时的方式处理,优化后的代码如下:
const LARGE_NUMBER = 100000000;
const DELAYED_TIME = 1000;
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
aboutToAppear(): void {
console.log('aboutToAppear');
this.computeTaskAsync();
}
...
computeTask(): void {
let count = 0;
while (count < LARGE_NUMBER) {
count++;
}
}
// 运算任务异步处理
private computeTaskAsync(): void {
setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行
this.computeTask();
}, DELAYED_TIME);
}
}
然后重新编译运行程序以及录制Launch,可以看到优化后UI Ability OnForeground阶段耗时大幅度缩短,如下图所示:
查看首帧卡顿
为了识别首帧是否卡顿,可以先在Launch的Frame泳道进行查看。应用的首帧渲染提交在First Frame - App Phase阶段,APP侧下面的这一帧表示应用渲染的首帧,如下图所示,此处首帧为36号帧:
如上所示36号帧被标记为了红色,表示首帧出现了卡顿。鼠标左键36号帧,可以看到它的期望提交渲染时间为左边白色竖线区域所示,这里出现了比较严重的延时。发现问题后,开发者可以参考前面讲到的示例进行问题定位和优化。
提升应用冷启动速度
本文将通过公共类优化的方法,包括 非冷启动必须的服务或模块延迟加载 、 减少主线程非UI耗时操作 和 网络请求提前发送 ,以及结合应用启动的几个阶段分别介绍提升应用冷启动速度的相关方法。
非冷启动必须的服务或模块延迟加载
应用在启动前加载过多不必要启动项,同时这些启动项在主线程串行执行,该阶段耗时接近450ms。
应用冷启动过程中,加载自身不必要的串行启动项,会导致冷启动耗时增加。建议延后加载或者并行处理。
减少主线程非UI耗时操作
在应用启动流程中,主要聚焦在执行UI相关操作中,为了更快的显示首页内容,不建议在主线程中执行非UI相关的耗时操作,建议通过异步任务进行延迟处理或放到其他子线程中执行。
在冷启动过程中如果存在图片下载、网络请求前置数据、数据反序列化等非UI操作开发者可以根据实际情况移至子线程中进行。