1# 提升应用冷启动速度
2
3应用启动时延是影响用户体验的关键要素。当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动。
4
5## 分析应用冷启动耗时
6
7应用冷启动过程大致可分成以下五个阶段:应用进程创建&初始化、Application&Ability初始化、Ability/AbilityStage生命周期、加载绘制首页、网络数据二次刷新,如下图:
8
9![](figures/application-cold-start.png)
10
111. **应用进程创建&初始化阶段**:该阶段主要是系统完成应用进程的创建以及初始化的过程,包含了启动页图标(startWindowIcon)的解码。
122. **Application&Ability初始化**:该阶段主要是资源加载、虚拟机创建、Application&Ability相关对象的创建与初始化、依赖模块的加载等。
133. **Ability/AbilityStage生命周期**:该阶段主要是AbilityStage/Ability的启动生命周期,执行相应的生命周期回调。
144. **加载绘制首页**:该阶段主要是加载首页内容、测量布局、刷新组件并绘制。
155. **网络数据二次刷新**:该阶段主要是应用根据业务需要对网络数据进行请求、处理、二次刷新。
16
17可见如果想要提升应用冷启动速度,需要缩短以上几个阶段的耗时。
18
19>**说明:**
20>
21> 1. 关于本文中示例,可参考:[提升应用冷启动速度示例](https://gitee.com/openharmony/applications_app_samples/tree/master/code/DocsSample/Ability/Performance/Startup)22> 2. 如何使用SmartPerf工具分析冷启动可参考:[应用冷启动分析](performance-optimization-using-smartperf-host.md#应用冷启动分析)。
23
24
25## 1、缩短应用进程创建&初始化阶段耗时
26
27该阶段主要是系统完成应用进程的创建以及初始化的过程,包含了启动页图标(startWindowIcon)的解码。
28
29### 设置合适分辨率的startWindowIcon
30
31该优化场景仅支持rk3568开发板。如果启动页图标分辨率过大,解码耗时会影响应用的启动速度,建议启动页图标分辨率不超过256像素*256像素,如下所示:
32
33```json
34    "abilities": [
35      {
36        "name": "EntryAbility",
37        "srcEntry": "./ets/entryability/EntryAbility.ets",
38        "description": "$string:EntryAbility_desc",
39        "icon": "$media:icon",
40        "label": "$string:EntryAbility_label",
41        "startWindowIcon": "$media:startIcon", // 在这里修改启动页图标,建议不要超过256像素x256像素
42        "startWindowBackground": "$color:start_window_background",
43        "exported": true,
44        "skills": [
45          {
46            "entities": [
47              "entity.system.home"
48            ],
49            "actions": [
50              "action.system.home"
51            ]
52          }
53        ]
54      }
55    ]
56```
57
58下面使用[SmartPerf](https://gitee.com/openharmony/developtools_smartperf_host)工具,对使用优化前的启动页图标(4096像素\*4096像素)及使用优化后的启动页图标(144像素\*144像素)的启动性能进行对比分析。分析阶段的起点为点击应用图标打开应用时触发的触摸事件(即`ProcessTouchEvent`的结束点),阶段终点为应用第一次接到vsync(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
59
60对比数据如下(性能耗时数据因设备版本而异,以实测为准):
61
62| 方案          | 阶段时长(毫秒) |
63|-------------|:--------:|
64| 使用优化前的启动页图标 |  998.6   |
65| 使用优化后的启动页图标 |  722.5   |
66
67可见阶段时长已缩短,故设置合适分辨率的startWindowIcon对缩短应用进程创建&初始化阶段耗时是有效的。
68
69## 2、缩短Application&Ability初始化阶段耗时
70
71该阶段主要是资源加载、虚拟机创建、Application&Ability相关对象的创建与初始化、依赖模块的加载等。
72主要耗时点在于资源加载阶段,分为主要的三个步骤:文件加载、依赖模块解析、文件执行。
731. 文件加载:查找并解析所有的文件到模块中记录。
742. 依赖模块解析(实例化):分配内存空间来存放模块所有导出的变量,但这时候内存中并没有分配变量的值。
753. 文件执行:运行ets文件,将内存中之前未分配值的变量赋为真实的值。
76
77下面将针对这三个阶段可能存在的优化手段进行详细展开说明。
78
79### 减少使用嵌套export *的方式全量导出
80应用冷启动过程中,会在**HandleLaunchAbility**中执行冷启动相关.ets文件,所有被主页面import的.ets文件均会被执行,包括数据结构、变量、全局函数的初始化等。首页需要用到的变量及函数等可能来源于其他ets文件,通过export的形式提供给首页使用。
81例:Numbers文件导出`export One`,需要在MainPage.ets中使用,尽量直接导入或者只嵌套一层Index文件,即在MainPage.ets中直接`import { One } from './Numbers'`。避免在Utils文件`export * from './Numbers'`,在SecondPage文件再次`export * from './Utils'`,最后在A文件中`import * from './SecondPage'`。
82以下为示例代码:
83【优化前】存在多层嵌套export *的方式全量导出
84```ts
85// Numbers.ets
86export const One: number = 1;
87
88// ...
89// 此处嵌套多层export *
90
91// Utils.ets
92export * from './Numbers';
93
94// SecondPage.ets
95export * from './Utils';
96
97// Index.ets
98import * from './SecondPage';
99```
100【优化后】不存在嵌套export *,从目标文件中直接import
101```ts
102// 去掉冗余嵌套的export,即在Index.ets中直接import { One } from './Numbers'。
103
104// Numbers.ets
105export const  One: number = 1;
106
107// Index.ets
108import { One } from './Numbers';
109```
110由于依赖模块解析采用深度优先遍历的方式来遍历模块依赖关系图中每一个模块记录,会先从入口文件的第一个导入语句开始一层层往更深层查找,直到最后一个没有导入语句的模块为止,连接好这个模块的导出变量之后会回到上一级的模块继续这个步骤,因此多层export *的使用会导致依赖模块解析、文件执行阶段耗时增长。
111针对上述示例代码关注该阶段耗时差异,对优化前后启动性能进行对比分析。分析阶段的起点为开始加载abc文件(即`H:JSPandaFileExecutor::ExecuteFromAbcFile`),阶段终点为`abc文件`加载完成。
112
113【优化前】存在8层嵌套export *
114![](./figures/application_coldstart1.png)
115
116【优化后】不存在嵌套export *,从目标文件中直接import
117![](./figures/application_coldstart2.png)
118
119对比数据如下:
120
121| 方案          |  阶段时长(微秒)  |
122|-------------|:----------:|
123| (优化前)存在8层嵌套export * |   492.6    |
124| (优化后)不存在嵌套export *,从目标文件中直接import |   388.7    |
125
126可见阶段时长已缩短。因此减少多层文件的嵌套导出export *可以提升应用冷启动速度。
127
128>**说明:**
129>
130>本示例中嵌套层次较浅,从时间上观测到的收益不明显,当实际开发过程中可能会涉及到更加复杂的情况,修改后对性能收益会更明显。
131
132### 减少import *的方式全量引用
133应用程序加载过程中,需要使用不同模块中的变量或函数,通常应用开发者会将相同类型的变量或函数放在同一个工具类文件当中,使用时通过import的方式引入对应的模块,当工具类中存在较多暴露函数或变量时,推荐直接import对应的变量,可以减少该阶段中.ets文件执行耗时,即减少文件中所有export变量的初始化过程。
134以下为示例代码:
135【优化前】Index.ets中使用 import * as nm from '../utils/Numbers'。
136```ts
137// Index.ets
138import * as nm from '../utils/Numbers'; // 不推荐import *的方式
139hilog.info(0x0000, 'testTag', '%{public}d', nm.One); // 此处仅用到变量One
140
141// Numbers.ets
142export const One: number = 1;
143export const Two: number = 2;
144// ...
145// 此处省略2000条数据
146```
147
148【优化后】Index.ets中使用 import { One } from '../utils/Numbers'。
149```ts
150// Index.ets
151import { One } as nm from '../utils/Numbers'; // 推荐按需引用变量
152hilog.info(0x0000, 'testTag', '%{public}d', One); // 此处仅用到变量One
153
154// Numbers.ets
155export const One: number = 1;
156export const Two: number = 2;
157// ...
158// 此处省略2000条数据
159```
160下面对优化前后启动性能进行对比分析,分析阶段的起点为UI Ability Launching开始(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility(const std::shared_ptr<AbilityLocalRecord> &`)的开始点),阶段终点为UI Ability Launching结束(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility(const std::shared_ptr<AbilityLocalRecord> &)`的结束点)。
161
162【优化前】使用import * as nm全量引用2000条数据
163![](./figures/application_coldstart3.png)
164
165【优化后】使用import { One }按需引用
166![](./figures/application_coldstart4.png)
167
168对比数据如下:
169
170| 方案          |   阶段时长(毫秒)    |
171|-------------|:-------------:|
172| (优化前)使用import * as nm全量引用 |     16.7      |
173| (优化后)使用import { One }按需引用 |      7.1      |
174
175可见阶段时长已缩短。因此将非UI耗时操作移至子线程中处理,可以缩短应用冷启动完成时延。
176
177>**说明:**
178>
179> **此优化方案仅可将冷启动阶段耗时缩短,但是可能导致其他场景耗时增长,即变量初始化过程从冷启动阶段分摊至其他使用阶段**。
180> 例:二级页面使用到Number中Two变量,此方案会使二级页面跳转过程对比优化前耗时更长。
181
182### 减少使用未引用的import模块
183
184应用代码执行前,应用程序必须找到并加载import的所有模块。应用程序启动时会因加载并使用的每个额外第三方框架或模块而增加启动耗时,耗时长短取决于加载的第三方框架或者模块的数量和大小。推荐开发者尽可能使用系统提供的模块,按需加载,来缩短应用程序的启动耗时。
185
186以下为示例代码:
187
188```ts
189// 优化减少import的模块
190// import { particleAbility, featureAbility, wantConstant } from '@kit.AbilityKit';
191// import { FormExtensionAbility } from '@kit.FormKit';
192// import { GesturePath, GesturePoint } from '@kit.AccessibilityKit';
193// import { distributedAccount, BusinessError, osAccount } from '@kit.BasicServicesKit';
194// import { webview } from '@kit.ArkWeb';
195
196import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
197import { window } from '@kit.ArkUI';
198import { hilog } from '@kit.PerformanceAnalysisKit';
199
200export default class EntryAbility extends UIAbility {
201  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
202    // ...
203    // webview.WebviewController.initializeWebEngine();
204  }
205  // ...
206}
207```
208
209需要说明,这里为了方便对比该阶段导入模块和不导入模块的性能差异,用一个@ohos.web.webview模块进行对比,这里假设@ohos.web.webview模块并非该阶段必须导入的模块。而在实际业务场景中,由于webview初始化时间较长,如果放到页面跳转时再导入该模块进行使用,有可能会劣化页面跳转的完成时延。因此,模块是否能在该阶段进行优化还需要结合具体业务场景进行评估。
210
211下面使用DevEco Studio内置的Profiler中的启动分析工具Launch,对优化import的模块前(导入@ohos.web.webview模块)及优化import的模块后(不导入@ohos.web.webview模块)的启动性能进行对比分析。下面是Launch工具抓取的UI Ability Launching(加载UI Ability)耗时,对比数据如下(性能耗时数据因设备型号版本而异,以实测为准):
212
213| 方案           | UI Ability Launching阶段时长(毫秒) |
214|--------------|:----------------------------:|
215| 优化import的模块前 |              26              |
216| 优化import的模块后 |              10              |
217
218可见阶段时长已缩短,故减少import的模块对缩短Application&Ability初始化阶段耗时是有效的。这里建议该阶段应减少应用启动时非必要的import模块,按需加载模块,缩短应用程序的启动耗时。
219
220### 合理拆分导出文件,减少冗余文件执行
221应用程序加载模块后,需要执行应用侧的.ets文件,对其进行初始化,并执行全局初始化变量、函数。可以将文件分为两类,一类为冷启动强相关文件(如首页展示界面及组件相关文件),一类为非冷启动强相关文件(如跳转后二级页面),在冷启动过程中仅执行冷启动强相关文件,来缩短应用的启动耗时。
222**场景示例:**
223应用存在两个页面,首页Index展示为HAR包中**MainPage.ets**的Text组件,该文件中不包含耗时操作;首页点击Text跳转至**SecondPage**,其中引用了HAR包中的**SubPage.ets**,该文件存在全局函数的耗时操作,会在模块加载时执行。但是HAR包中的导出文件Index.ets同时导出了**MainPage.etsSubPage.ets**,而首页直接import { MainPage } from 'library/Index'的方式会导致应用在冷启动过程中执行了非冷启动强相关文件**SubPage.ets**,增加了冷启动耗时。
224
225【优化前】加载模块时执行了非冷启动相关文件SubPage.ets
226
227![](./figures/application_coldstart5.png)
228
229以下为示例代码:
230```ts
231// entry/src/main/ets/pages/Index.ets
232import { MainPage } from 'library/Index'; // 不推荐用法:直接导入了与冷启动非强相关文件SubPage.ets
233export struct Index{
234  @Provide pathStack: NavPathStack = new NavPathStack();
235  build() {
236    Navigation(this.pathStack) {
237      Row() {
238        // 引用HAR的自定义组件
239        MainPage()
240      }
241    }
242  }
243}
244
245// library/src/main/ets/components/mainpage/MainPage.ets
246@Component
247export struct MainPage {
248  @Consume pathStack: NavPathStack;
249  @State message: string = 'HAR MainPage';
250  build() {
251    Row() {
252      Text(this.message)
253        .fontSize(32)
254        .fontWeight(FontWeight.Bold)
255    }.onClick(() => {
256      this.pathStack.pushPath({ name: 'SecondPage' });
257    })
258  }
259}
260
261// entry/src/main/ets/pages/SecondPage.ets
262import { SubPage } from 'library/Index';
263@Builder
264export function SecondPageBuilder() {
265  SecondPage()
266}
267@Entry
268@Component
269struct SecondPage {
270  pathStack: NavPathStack = new NavPathStack();
271  build() {
272    NavDestination() {
273      Row() {
274        // 引用HAR的自定义组件
275        SubPage()
276      }
277      .height('100%')
278    }
279    .onReady((context: NavDestinationContext) => {
280      this.pathStack = context.pathStack;
281    })
282  }
283}
284
285// library/src/main/ets/components/mainpage/SubPage.ets
286// SubPage中的全局耗时函数
287const LARGE_NUMBER = 10000000;
288function computeTask(): number {
289  let count = 0;
290  while (count < LARGE_NUMBER) {
291    count++;
292  }
293  return count;
294}
295let count = computeTask();
296// ...
297
298// library/Index.ets
299export { MainPage } from './src/main/ets/components/mainpage/MainPage'; // 冷启动强相关文件
300export { SubPage } from './src/main/ets/components/mainpage/SubPage'; // 非冷启动强相关文件
301```
302
303**【优化方案一】**
304将HAR包的导出文件**Index.ets**进行拆分,**IndexAppStart.ets**文件仅导出首页相关文件,即**MainPage.ets**。**IndexOthers.ets**文件导出非首页相关文件,即**SubPage.ets。**
305**优点**:使用此种方案优化后可以将冷启阶段(加载首页文件)与非冷启阶段(加载非首页文件)需要执行的.ets文件进行完全拆分,类比其他需优化的场景也可以使用本方案进行拆分。
306**缺点**:需保证拆分后IndexAppStart.ets中的导出文件不存在对于IndexOthers.ets中的导出文件的引用。
307
308【图一】拆分HAR导出文件
309
310![](./figures/application_coldstart6.png)
311
312以下为示例代码:
3131. 将HAR包的导出文件Index.ets进行拆分,IndexAppStart.ets文件仅导出首页相关文件,IndexOthers.ets文件导出非首页相关文件。
314```ts
315// library/IndexAppStart.ets
316export { MainPage } from './src/main/ets/components/mainpage/MainPage';
317// library/IndexOthers.ets
318export { SubPage } from './src/main/ets/components/mainpage/SubPage';
319```
3202. 首页Index从IndexAppStart.ets导入MainPage。
321```ts
322// Index.ets
323import { MainPage } from 'library/IndexAppStart';
324
325@Entry
326@Component
327struct Index {
328  @Provide pathStack: NavPathStack = new NavPathStack();
329
330  build() {
331    Navigation(this.pathStack) {
332      Row() {
333        // 引用HAR的自定义组件
334        MainPage()
335      }
336    }
337    .height('100%')
338    .width('100%')
339  }
340}
341```
3423. 跳转后的页面SecondPage从IndexOthers.ets导入SubPage。
343```ts
344// SecondPage.ets
345import { SubPage } from 'library/IndexOthers';
346
347@Builder
348export function SecondPageBuilder() {
349  SecondPage()
350}
351
352@Entry
353@Component
354struct SecondPage {
355  pathStack: NavPathStack = new NavPathStack();
356
357  build() {
358    NavDestination() {
359      Row() {
360        // 引用HAR的自定义组件
361        SubPage()
362      }
363      .height('100%')
364    }
365    .onReady((context: NavDestinationContext) => {
366      this.pathStack = context.pathStack;
367    })
368  }
369}
370```
371**【优化方案二】**
372在首页的**Index.ets**文件中导入**MainPage.ets**时使用全路径展开。
373**优点**:不需要新增文件来汇总导出所有冷启阶段文件。
374**缺点**:引用时需要对所有冷启阶段文件进行路径展开,增加开发和维护成本。
375
376【图二】首页导入冷启动文件时使用全路径展开
377
378![](./figures/application_coldstart7.png)
379
380以下为示例代码:
381```ts
382// Index.ets
383import { MainPage } from 'library/src/main/ets/components/mainpage/MainPage';
384
385@Entry
386@Component
387struct Index {
388  @Provide pathStack: NavPathStack = new NavPathStack();
389
390  build() {
391    Navigation(this.pathStack) {
392      Row() {
393        // 引用HAR的自定义组件
394        MainPage()
395      }
396    }
397    .height('100%')
398    .width('100%')
399  }
400}
401```
402>**说明:**
403>
404>1. **上述两种优化方案默认MainPage中不存在对于SubPage中的import。**
405>2. **当存在MainPage对于SubPage的直接import时,需要使用[动态import](../arkts-utils/arkts-dynamic-import.md)方法来进行优化。**
406>3. 开发者可自行根据优化方案的优缺点权衡选择合适的优化方案。
407
408下面对优化前后启动性能进行对比分析。阶段起点为`UI Ability Launching`的开始点,阶段终点为应用首帧即`First Frame - App Phase`的开始点。
409
410【优化前】加载模块时执行了非冷启动相关文件
411![](./figures/application_coldstart8.png)
412
413【优化方案一】拆分HAR导出文件
414![](./figures/application_coldstart9.png)
415
416【优化方案二】导入冷启动文件时全路径展开
417![](./figures/application_coldstart10.png)
418
419优化前后的对比数据如下:
420
421| 方案        |  阶段时长(毫秒)  |
422|-----------|:----------:|
423| 优化前 |   140.1    |
424| 优化方案一(拆分HAR导出文件) |    62.9    |
425| 优化方案二(导入冷启动文件时全路径展开) |    61.3    |
426
427可见阶段时长已缩短,因此可以通过拆分HAR包导出的Index.ets文件或导入冷启动文件时路径全展开的方案,减少应用冷启动中.ets文件执行耗时,从而提升应用冷启动速度。
428
429### 使用延迟加载Lazy-Import减少冷启动冗余文件执行
430
431可以通过延迟加载 [lazy-import](../arkts-utils/arkts-lazy-import.md) 延缓对冷启动时暂不执行的冗余文件的加载,而在后续导出变量被真正使用时再同步加载执行文件,节省资源以提高应用冷启动性能。
432详细使用指导请参考[延迟加载lazy-import使用指导](Lazy-Import-Instructions.md)。
433
434### 减少多个HSP/HAP对于相同HAR的引用
435
436在应用开发的过程中,可以使用[HSP](../quick-start/in-app-hsp.md)或[HAR](../quick-start/har-package.md)的共享包方式将同类的模块进行整合,用于实现多个模块或多个工程间共享ArkUI组件、资源等相关代码。
437由于共享包的动态和静态差异,在多HAP/HSP引用相同HAR包的情况下,会存在HAR包中的单例失效,从而影响到应用冷启动的性能。
438
439【优化前】HAP包和HSP包分别引用相同HAR包
440
441![](./figures/application_coldstart11.png)
442
443如上图所示,工程内存在三个模块,HAP包为应用主入口模块,HSP为应用主界面显示模块,HAR_COMMON集成了所有通用工具类,其中funcResult为func方法的执行结果。
444由于HAP和HSP模块同时引用HAR_COMMON模块时会破坏HAR的单例模式,所以HAP和HSP模块使用**HAR_COMMON**中的**funcResult**时,会导致func方法在两个模块加载时各执行一次,使得文件执行时间耗时增长。
445如果仅从性能的角度考虑,可以使用以下方式进行修改,从而达到缩短冷启动阶段耗时的目的。
446
447【优化后】切换为HAP包和HAR包分别引用相同HAR包
448
449![](./figures/application_coldstart12.png)
450
451>**说明:**
452>
453> 1. 在多HAP/HSP引用相同HAR包的情况下,若HSP包和HAR包均能满足业务需求,建议将HSP包改成HAR包。
454> 2. 若使用的HSP为集成态HSP,可跳过该优化方案。
455
456以下为示例代码:
4571. 在被引用HAR_COMMON包中写入功能示例。
458```ts
459// har_common/src/main/ets/utils/Utils.ets
460const LARGE_NUMBER = 100000000;
461function func(): number {
462  let count = 0;
463  while (count < LARGE_NUMBER) {
464    count++;
465  }
466  return count;
467}
468export let funcResult = func();
469```
4702. 分别通过使用HSP包和HAR包来引用该HAR_COMMON包中的功能进行性能对比实验。
471- 使用HAP包和HSP包引用该HAR_COMMON包中的功能。
472  HAP包引用HAR_COMMON包中的功能。
473
474  ```ts
475  // entry/src/main/ets/pages/Index.ets
476  import { MainPage } from 'hsp_library';
477  import { funcResult } from 'har_common';
478  ```
479  HSP包引用HAR_COMMON包中的功能。
480  ```ts
481  // hsp_library/src/main/ets/pages/MainPage.ets
482  import { funcResult } from 'har_common';
483  ```
484- 使用HAP包和HAR包引用该HAR_COMMON包中的功能。
485  HAP包引用HAR_COMMON包中的功能。
486  ```ts
487  // entry/src/main/ets/pages/Index.ets
488  import { MainPage } from 'har_library';
489  import { funcResult } from 'har_common';
490  ```
491  HAR包引用HAR_COMMON包中的功能。
492  ```ts
493  // har_library/src/main/ets/pages/MainPage.ets
494  import { funcResult } from 'har_common';
495  ```
496下面对优化前后启动性能进行对比分析。分析阶段的起点为启动Ability(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility`的开始点),阶段终点为应用第一次接到vsync(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
497
498【优化前】使用HSP包
499![](./figures/application_coldstart13.png)
500
501【优化后】使用HAR代替HSP
502![](./figures/application_coldstart14.png)
503
504优化前后的对比数据如下:
505
506| 方案               |  阶段时长(毫秒)  |
507|------------------|:----------:|
508| (优化前)使用HSP包      |    3125    |
509| (优化后)使用HAR代替HSP |   853.9    |
510
511从测试数据可以看出,将HSP替换为HAR包后,应用启动的阶段耗时明显缩短。
512
513>**说明:**
514>
515> 上述示例为凸显出差异,func执行函数循环次数为100000000,开发者实际修改后收益需根据实际情况测试。
516
517
518## 3、缩短AbilityStage生命周期阶段耗时
519
520该阶段主要是AbilityStage的启动生命周期,执行相应的生命周期回调。
521
522### 避免在AbilityStage生命周期回调接口进行耗时操作
523
524在应用启动流程中,系统会执行AbilityStage的生命周期回调函数。因此,不建议在这些回调函数中执行耗时过长的操作,耗时操作建议通过异步任务延迟处理或者放到其他线程执行。
525
526在这些生命周期回调里,推荐开发者只做必要的操作,详情可以参考:[AbilityStage组件容器](../application-models/abilitystage.md)。
527
528以下为示例代码:
529
530```ts
531const LARGE_NUMBER = 10000000;
532const DELAYED_TIME = 1000;
533
534export default class MyAbilityStage extends AbilityStage {
535  onCreate(): void {
536    // 耗时操作
537    // this.computeTask();
538    this.computeTaskAsync(); // 异步任务
539  }
540
541  onAcceptWant(want: Want): string {
542    // 仅specified模式下触发
543    return 'MyAbilityStage';
544  }
545
546  computeTask(): void {
547    let count = 0;
548    while (count < LARGE_NUMBER) {
549      count++;
550    }
551  }
552
553  private computeTaskAsync(): void {
554    setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行
555      this.computeTask();
556    }, DELAYED_TIME);
557  }
558}
559```
560
561下面使用[SmartPerf](https://gitee.com/openharmony/developtools_smartperf_host)工具,对优化前同步执行耗时操作及优化后异步执行耗时操作的启动性能进行对比分析。分析阶段的起点为启动Ability(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility`的开始点),阶段终点为应用第一次接到vsync(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
562
563对比数据如下:
564
565|                        | 阶段开始(秒)   | 阶段结束(秒)   | 阶段时长(秒) |
566| ---------------------- | -------------- | -------------- | ------------ |
567| 优化前同步执行耗时操作 | 2124.915558194 | 2127.041354575 | 2.125796381  |
568| 优化后异步执行耗时操作 | 4186.436835246 | 4186.908777335 | 0.471942089  |
569
570可见阶段时长已缩短,故避免在AbilityStage生命周期回调接口进行耗时操作对缩短AbilityStage生命周期阶段耗时是有效的。
571
572## 4、缩短Ability生命周期阶段耗时
573
574该阶段主要是Ability的启动生命周期,执行相应的生命周期回调。
575
576### 非UI耗时操作并行化
577在应用启动流程中,主要聚焦在执行UI相关操作中,为了更快的能显示首页内容,不建议在主线程中执行非UI相关的耗时操作,耗时操作建议通过异步任务进行延迟处理或放到其他子线程中执行,线程并发方案可以参考:[TaskPool和Worker的对比实践](../arkts-utils/multi-thread-concurrency-overview.md)。
578在冷启动过程中如果存在图片下载、网络请求前置数据、数据反序列化等非UI操作可以根据开发者实际情况移至子线程中进行,参考下面文章:[避免在主线程中执行耗时操作](avoid_time_consuming_operations_in_mainthread.md)。
579
580### 避免在Ability生命周期回调接口进行耗时操作
581
582在应用启动流程中,系统会执行Ability的生命周期回调函数。因此,不建议在这些回调函数中执行耗时过长的操作,耗时操作建议通过异步任务延迟处理或者放到其他线程执行。
583
584在这些生命周期回调里,推荐开发者只做必要的操作,下面以UIAbility为例进行说明。关于UIAbility组件生命周期的详细说明,参见[UIAbility组件生命周期](../application-models/uiability-lifecycle.md)。
585
586```ts
587const LARGE_NUMBER = 10000000;
588const DELAYED_TIME = 1000;
589
590export default class EntryAbility extends UIAbility {
591  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
592    hilog.info(0x0000, 'testTag', 'Ability onCreate');
593    // 耗时操作
594    // this.computeTask();
595    this.computeTaskAsync(); // 异步任务
596  }
597
598  onDestroy(): void {
599    hilog.info(0x0000, 'testTag', 'Ability onDestroy');
600  }
601
602  onWindowStageCreate(windowStage: window.WindowStage): void {
603    hilog.info(0x0000, 'testTag', 'Ability onWindowStageCreate');
604
605    windowStage.loadContent('pages/Index', (err, data) => {
606      if (err.code) {
607        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: ' + JSON.stringify(err) ?? '');
608        return;
609      }
610      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: ' + JSON.stringify(data) ?? '');
611    });
612
613    // 耗时操作
614    // this.computeTask();
615    this.computeTaskAsync(); // 异步任务
616  }
617
618  onWindowStageDestroy(): void {
619    hilog.info(0x0000, 'testTag', 'Ability onWindowStageDestroy');
620  }
621
622  onForeground(): void {
623    hilog.info(0x0000, 'testTag', 'Ability onForeground');
624    // 耗时操作
625    // this.computeTask();
626    this.computeTaskAsync(); // 异步任务
627  }
628
629  onBackground(): void {
630    hilog.info(0x0000, 'testTag', 'Ability onBackground');
631  }
632
633  computeTask(): void {
634    let count = 0;
635    while (count < LARGE_NUMBER) {
636      count++;
637    }
638  }
639
640  private computeTaskAsync(): void {
641    setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行
642      this.computeTask();
643    }, DELAYED_TIME);
644  }
645}
646```
647
648下面使用[SmartPerf](https://gitee.com/openharmony/developtools_smartperf_host)工具,对优化前同步执行耗时操作及优化后异步执行耗时操作的启动性能进行对比分析。分析阶段的起点为启动Ability(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility`的开始点),阶段终点为应用第一次接到vsync(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
649
650对比数据如下:
651
652| 方案          | 阶段开始(秒)   | 阶段结束(秒)   | 阶段时长(秒) |
653|-------------| -------------- | -------------- | ------------ |
654| 优化前同步执行耗时操作 | 1954.987630036 | 1957.565964504 | 2.578334468  |
655| 优化后异步执行耗时操作 | 4186.436835246 | 4186.908777335 | 0.471942089  |
656
657可见阶段时长已缩短,故避免在Ability生命周期回调接口进行耗时操作对缩短Ability生命周期阶段耗时是有效的。
658
659## 5、缩短加载绘制首页阶段耗时
660
661该阶段主要是加载首页内容、测量布局、刷新组件并绘制。
662
663### 自定义组件生命周期回调接口里避免耗时操作
664
665自定义组件的生命周期变更会调用相应的回调函数。
666
667aboutToAppear函数会在创建自定义组件实例后,页面绘制之前执行,以下代码在aboutToAppear中对耗时间的计算任务进行了异步处理,避免在该接口执行该耗时操作,不阻塞页面绘制。
668
669以下为示例代码:
670
671```ts
672const LARGE_NUMBER = 10000000;
673const DELAYED_TIME = 1000;
674
675@Entry
676@Component
677struct Index {
678  @State private text: string = "";
679  private count: number = 0;
680
681  aboutToAppear() {
682    // 耗时操作
683    // this.computeTask();
684    this.computeTaskAsync(); // 异步任务
685    let context = getContext(this) as Context;
686    this.text = context.resourceManager.getStringSync($r('app.string.startup_text'));
687  }
688
689  build() {
690    Column({ space: 10 }) {
691      Text(this.text).fontSize(50)
692    }
693    .width('100%')
694    .height('100%')
695    .padding(10)
696  }
697
698  computeTask(): void {
699    this.count = 0;
700    while (this.count < LARGE_NUMBER) {
701      this.count++;
702    }
703    let context = getContext(this) as Context;
704    this.text = context.resourceManager.getStringSync($r('app.string.task_text'));
705  }
706
707  // 运算任务异步处理
708  private computeTaskAsync(): void {
709    setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行
710      this.computeTask();
711    }, DELAYED_TIME);
712  }
713}
714```
715
716下面使用[SmartPerf](https://gitee.com/openharmony/developtools_smartperf_host)工具,对优化前同步执行耗时操作及优化后异步执行耗时操作的启动性能进行对比分析。分析阶段的起点为启动Ability(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility`的开始点),阶段终点为应用第一次接到vsync(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
717
718对比数据如下:
719
720| 方案          | 阶段开始(秒)   | 阶段结束(秒)   | 阶段时长(秒) |
721|-------------| -------------- | -------------- | ------------ |
722| 优化前同步执行耗时操作 | 3426.272974492 | 3431.785898837 | 5.512924345  |
723| 优化后异步执行耗时操作 | 4186.436835246 | 4186.908777335 | 0.471942089  |
724
725可见阶段时长已缩短,因此在自定义组件生命周期回调接口中避免耗时操作可以缩短加载绘制首页阶段耗时。
726
727## 6、缩短网络数据二次刷新阶段耗时
728
729该阶段主要是应用根据业务需要对网络数据进行请求、处理、二次刷新。
730
731### 网络请求提前发送
732当前大多数应用的首页内容需从网络获取,发送网络请求的时机显得尤为重要。应用发送网络请求后等待网络数据的返回,网络请求的这段时间应用可以继续执行启动流程,直到网络数据返回后进行解析,反序列化之后就可以加载首页数据,因此网络请求的发起时机越早,整个冷启动的完成时延阶段越短。
733
734可将网络请求及网络请求前的初始化流程放置在AbilityStage/UIAbility的onCreate()生命周期中,在AbilityStage/UIAbility中仅执行网络相关预处理,等待网络请求发送后可继续执行首页数据准备、UI相关操作。在服务端处理流程相同的情况下,应用可以更早的拿到网络数据并行展示。
735
736【优化前】应用首页框架加载时进行网络数据请求
737![](./figures/application_coldstart15.png)
738
739将网络请求提前至AbilityStage/UIAbility生命的onCreate()生命周期回调函数中,可以将首刷或二刷的时间提前,减少用户等待时间。此处为了体现性能收益,将网络请求放到了更早的AbilityStage的onCreate()生命周期回调中。
740
741【优化后】网络请求提前至AbilityStage的onCreate()周期回调中
742![](./figures/application_coldstart16.png)
743
744以下为示例代码:
745【优化前】:在首页根组件的onAppear()生命周期回调中发起网络请求。
746```ts
747// entry/src/main/ets/pages/Index.ets
748import { httpRequest } from '../utils/NetRequest';
749import { number } from '../utils/Calculator';
750
751AppStorage.link('netData');
752PersistentStorage.persistProp('netData', undefined);
753
754@Entry
755@Component
756struct Index {
757  @State message: string = 'Hello World' + number; // 为了体现性能收益,引用耗时函数的执行结果number
758  @StorageLink('netData') netData: PixelMap | undefined = undefined;
759  build(){
760    Row(){
761      Image(this.netData)
762        .objectFit(ImageFit.Contain)
763        .width('50%')
764        .height('50%')
765    }
766    .onAppear(() => {
767      // 发送网络请求
768      httpRequest();
769    })
770  }
771}
772
773// entry/src/main/ets/utils/NetRequest.ets
774import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
775import { http } from '@kit.NetworkKit';
776import { BusinessError } from '@kit.BasicServicesKit';
777import { image } from '@kit.ImageKit';
778// 通过http的request方法从网络下载图片资源
779export function httpRequest() {
780  hiTraceMeter.startTrace('Http Request', 1);
781  http.createHttp()
782    // 实际开发需要将"https://www.example1.com/POST?e=f&g=h"替换成为真实要访问的网站地址
783    .request('https://www.example1.com/POST?e=f&g=h',
784      (error: BusinessError, data: http.HttpResponse) => {
785        if (error) {
786          // 下载失败时不执行后续逻辑
787          return;
788        }
789        // 处理网络请求返回的数据
790        transcodePixelMap(data);
791      }
792    )
793}
794// 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型
795function transcodePixelMap(data: http.HttpResponse) {
796  if (http.ResponseCode.OK === data.responseCode) {
797    const imageData: ArrayBuffer = data.result as ArrayBuffer;
798    // 通过ArrayBuffer创建图片源实例
799    const imageSource: image.ImageSource = image.createImageSource(imageData);
800    const options: image.InitializationOptions = {
801      'alphaType': 0, // 透明度
802      'editable': false, // 是否可编辑
803      'pixelFormat': 3, // 像素格式
804      'scaleMode': 1, // 缩略值
805      'size': { height: 100, width: 100 }
806    }; // 创建图片大小
807    // 通过属性创建PixelMap
808    imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
809      AppStorage.set('netData', pixelMap);
810      hiTraceMeter.finishTrace('Http Request', 1);
811    });
812  }
813}
814
815// entry/src/main/ets/utils/Calculator.ets
816const LARGE_NUMBER = 100000000;
817function computeTask(): number {
818  let count = 0;
819  while (count < LARGE_NUMBER) {
820    count++;
821  }
822  return count;
823}
824export let number = computeTask();
825```
826
827【优化后】
8281. 在NetRequest.ets中进行Http请求以及数据处理。
829```ts
830// NetRequest.ets
831import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
832import { http } from '@kit.NetworkKit';
833import { BusinessError } from '@kit.BasicServicesKit';
834import { image } from '@kit.ImageKit';
835// 通过http的request方法从网络下载图片资源
836export function httpRequest() {
837  hiTraceMeter.startTrace('Http Request', 1);
838  http.createHttp()
839    // 实际开发需要将"https://www.example1.com/POST?e=f&g=h"替换成为真实要访问的网站地址
840    .request('https://www.example1.com/POST?e=f&g=h',
841      (error: BusinessError, data: http.HttpResponse) => {
842        if (error) {
843          // 下载失败时不执行后续逻辑
844          return;
845        }
846        // 处理网络请求返回的数据
847        transcodePixelMap(data);
848      }
849    )
850}
851
852// 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型
853function transcodePixelMap(data: http.HttpResponse) {
854  if (http.ResponseCode.OK === data.responseCode) {
855    const imageData: ArrayBuffer = data.result as ArrayBuffer;
856    // 通过ArrayBuffer创建图片源实例
857    const imageSource: image.ImageSource = image.createImageSource(imageData);
858    const options: image.InitializationOptions = {
859      'alphaType': 0, // 透明度
860      'editable': false, // 是否可编辑
861      'pixelFormat': 3, // 像素格式
862      'scaleMode': 1, // 缩略值
863      'size': { height: 100, width: 100 }
864    }; // 创建图片大小
865    // 通过属性创建PixelMap
866    imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
867      AppStorage.set('netData', pixelMap);
868      hiTraceMeter.finishTrace('Http Request', 1);
869    });
870  }
871}
872```
8732. 在AbilityStage的onCreate()生命周期回调中发起网络请求。
874```ts
875// MyAbilityStage.ets
876import { AbilityStage, Want } from '@kit.AbilityKit';
877import { httpRequest } from '../utils/NetRequest';
878export default class MyAbilityStage extends AbilityStage {
879  onCreate(): void {
880    // 发送网络请求
881    httpRequest();
882  }
883
884  onAcceptWant(want: Want): string {
885    // 仅specified模式下触发
886    return 'MyAbilityStage';
887  }
888}
889```
8903. 在首页Index.ets中展示请求获取的图片。
891```ts
892// Index.ets
893import { number } from '../utils/Calculator';
894
895AppStorage.link('netData');
896PersistentStorage.persistProp('netData', undefined);
897
898@Entry
899@Component
900struct Index {
901  @State message: string = 'Hello World' + number; // 为了体现性能收益,引用耗时函数的执行结果number
902  @StorageLink('netData') netData: PixelMap | undefined = undefined;
903  build() {
904    Row() {
905      Image(this.netData)
906        .objectFit(ImageFit.Contain)
907        .width('50%')
908        .height('50%')
909    }
910    .onDisAppear(() => {
911      AppStorage.set('netData', undefined);
912    })
913    .height('100%')
914    .width('100%')
915  }
916}
917```
918
919下面对优化前后启动性能进行对比分析,分析阶段的起点为启动Ability(即`H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility`的开始点),阶段终点为应用接收到网络数据返回后的首帧刷新(即`H:ReceiveVsync dataCount:24Bytes now:timestamp expectedEnd:timestamp vsyncId:int`的开始点)。
920
921【优化前】优化网络请求时机前
922![](./figures/application_coldstart17.png)
923
924【优化后】优化网络请求时机后
925![](./figures/application_coldstart18.png)
926
927对比数据如下:
928
929| 方案        |  阶段时长(毫秒)  |
930|-----------|:----------:|
931| 优化网络请求时机前 |    1700    |
932| 优化网络请求时机后 |   885.3    |
933
934因此,可以通过提前网络请求的方式减少应用冷启动耗时。
935
936### 使用本地缓存首页数据
937
938使用本地缓存首页数据是优化应用性能的关键一环。它能有效缩短冷启动时的白屏或白块时间,显著提升用户体验。该策略通过预先存储并优先展示缓存中的首页数据,减少了对外部资源(如网络)的依赖,从而加快数据加载速度。当数据更新时,应用则智能地从网络等渠道获取最新内容,确保信息的时效性与准确性。使用本地缓存首页数据,不仅让应用响应更迅速,还显著优化了整体运行流畅度,为用户带来更加顺畅的体验。更多实现细节与性能提升分析,请参见[合理使用缓存提升性能](./reasonable_using_cache_improve_performance.md)。