# 应用启动框架AppStartup
## 概述
应用启动时通常需要执行一系列初始化启动任务,如果将启动任务都放在应用主模块(即entry类型的[Module](../quick-start/application-package-overview.md#module类型))的[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)组件的[onCreate](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate)生命周期中,那么只能在主线程中依次执行,不但影响应用的启动速度,而且当启动任务过多时,任务之间复杂的依赖关系还会使得代码难以维护。
AppStartup提供了一种简单高效的应用启动方式,可以支持任务的异步启动,加快应用启动速度。同时,通过在一个配置文件中统一设置多个启动任务的执行顺序以及依赖关系,让执行启动任务的代码变得更加简洁清晰、容易维护。
启动框架支持以自动模式或手动模式执行启动任务,默认采用自动模式。在构造[AbilityStage组件容器](../reference/apis-ability-kit/js-apis-app-ability-abilityStage.md)过程中开始加载开发者配置的启动任务,并执行自动模式的启动任务。开发者也可以在UIAbility创建完后调用[startupManager.run](../reference/apis-ability-kit/js-apis-app-appstartup-startupManager.md#startupmanagerrun)方法,执行手动模式的启动任务。
**图1** 启动框架执行时机

## 约束限制
- 启动框架只支持在entry类型的[Module](../quick-start/application-package-overview.md#module类型)下的UIAbility中使用。
- 启动任务之间不允许存在循环依赖。
## 开发流程
1. [定义启动框架配置文件](#定义启动框架配置文件):在资源文件目录下创建启动框架配置文件、添加启动任务的配置信息,并在[module.json5](../quick-start/module-configuration-file.md)配置文件中引用。
2. [设置启动参数](#设置启动参数):在启动参数文件中,设置超时时间和启动任务的监听器等参数。
3. [为每个待初始化组件添加启动任务](#为每个待初始化组件添加启动任务):通过实现[StartupTask](../reference/apis-ability-kit/js-apis-app-appstartup-startupTask.md)接口,启动框架将会按顺序执行初始化流程。
## 开发步骤
### 定义启动框架配置文件
1. 在应用主模块(即entry类型的[Module](../quick-start/application-package-overview.md#module类型))的“resources/base/profile”路径下,新建启动框架配置文件。文件名可以自定义,本文以"startup_config.json"为例。
2. 在启动框架配置文件startup_config.json中,依次添加各个启动任务的配置信息。
假设当前应用启动框架共包含6个启动任务,任务之间的依赖关系如下图所示。为了便于并发执行启动任务,单个启动任务文件包含的启动任务应尽量单一,本例中每个启动任务对应一个启动任务文件。
**图2** 启动任务依赖关系图

1. 在“ets/startup”路径下,依次创建6个启动任务文件、以及一个公共的启动参数配置文件。文件名称必须确保唯一性。
1. 创建启动任务文件。本例中的6个文件名分别为StartupTask_001.ets~StartupTask_006.ets。
2. 创建启动任务参数配置文件。本例中的文件名为StartupConfig.ets。
2. 在启动框架配置文件startup_config.json中,添加所有启动任务以及启动参数配置文件的相关信息。
startup_config.json文件示例如下:
```json
{
"startupTasks": [
{
"name": "StartupTask_001",
"srcEntry": "./ets/startup/StartupTask_001.ets",
"dependencies": [
"StartupTask_002",
"StartupTask_003"
],
"runOnThread": "taskPool",
"waitOnMainThread": false
},
{
"name": "StartupTask_002",
"srcEntry": "./ets/startup/StartupTask_002.ets",
"dependencies": [
"StartupTask_004"
],
"runOnThread": "taskPool",
"waitOnMainThread": false
},
{
"name": "StartupTask_003",
"srcEntry": "./ets/startup/StartupTask_003.ets",
"dependencies": [
"StartupTask_004"
],
"runOnThread": "taskPool",
"waitOnMainThread": false
},
{
"name": "StartupTask_004",
"srcEntry": "./ets/startup/StartupTask_004.ets",
"runOnThread": "taskPool",
"waitOnMainThread": false
},
{
"name": "StartupTask_005",
"srcEntry": "./ets/startup/StartupTask_005.ets",
"dependencies": [
"StartupTask_006"
],
"runOnThread": "mainThread",
"waitOnMainThread": true,
"excludeFromAutoStart": true
},
{
"name": "StartupTask_006",
"srcEntry": "./ets/startup/StartupTask_006.ets",
"runOnThread": "mainThread",
"waitOnMainThread": false,
"excludeFromAutoStart": true
}
],
"configEntry": "./ets/startup/StartupConfig.ets"
}
```
**表1** startup_config.json配置文件标签说明
| 属性名称 | 含义 | 数据类型 | 是否可缺省 |
| -------- | -------- | -------- | -------- |
| startupTasks | 启动任务配置信息,标签说明详见下表。 | 对象数组 | 该标签不可缺省。 |
| configEntry | 启动参数配置文件所在路径。 | 字符串 | 该标签不可缺省。 |
**表2** startupTasks标签说明
| 属性名称 | 含义 | 数据类型 | 是否可缺省 |
| -------- | -------- | -------- | -------- |
| name | 启动任务对应的类名。 | 字符串 | 该标签不可缺省。 |
| srcEntry | 启动任务对应的文件路径。 | 字符串 | 该标签不可缺省。 |
| dependencies | 启动任务依赖的其他启动任务的类名数组。 | 对象数组 | 该标签可缺省,缺省值为空。 |
| excludeFromAutoStart | 是否排除自动模式,详细介绍可以查看[修改启动模式](#可选修改启动模式)。
- true:手动模式。
- false:自动模式。 | 布尔值 | 该标签可缺省,缺省值为false。 |
| runOnThread | 执行初始化所在的线程。
- `mainThread`:在主线程中执行。
- `taskPool`:在异步线程中执行。 | 字符串 | 该标签可缺省,缺省值为`mainThread`。 |
| waitOnMainThread | 主线程是否需要等待启动框架执行。当runOnThread取值为`taskPool`时,该字段生效。
- true:主线程等待启动框架执行完之后,才会加载应用首页。
- false:主线程不等待启动任务执行。 | 布尔值 | 该标签可缺省,缺省值为true。 |
3. 在[module.json5配置文件](../quick-start/module-configuration-file.md)的appStartup标签中,添加启动框架配置文件的索引。
module.json5示例代码如下。
```json
{
"module": {
"name": "entry",
"type": "entry",
// ...
"appStartup": "$profile:startup_config", // 启动框架的配置文件
// ...
}
}
```
### 设置启动参数
在启动参数配置文件(本文为“ets/startup/StartupConfig.ets”文件)中,使用[StartupConfigEntry](../reference/apis-ability-kit/js-apis-app-appstartup-startupConfigEntry.md)接口实现启动框架公共参数的配置,包括超时时间和启动任务的监听器等参数,其中需要用到如下接口:
- [StartupConfig](../reference/apis-ability-kit/js-apis-app-appstartup-startupConfig.md):用于设置任务超时时间和启动框架的监听器。
- [StartupListener](../reference/apis-ability-kit/js-apis-app-appstartup-startupListener.md):用于监听启动任务是否执行成功。
```ts
import { StartupConfig, StartupConfigEntry, StartupListener } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
export default class MyStartupConfigEntry extends StartupConfigEntry {
onConfig() {
hilog.info(0x0000, 'testTag', `onConfig`);
let onCompletedCallback = (error: BusinessError) => {
hilog.info(0x0000, 'testTag', `onCompletedCallback`);
if (error) {
hilog.info(0x0000, 'testTag', 'onCompletedCallback: %{public}d, message: %{public}s', error.code, error.message);
} else {
hilog.info(0x0000, 'testTag', `onCompletedCallback: success.`);
}
};
let startupListener: StartupListener = {
'onCompleted': onCompletedCallback
};
let config: StartupConfig = {
'timeoutMs': 10000,
'startupListener': startupListener
};
return config;
}
}
```
### 为每个待初始化组件添加启动任务
上述操作中已完成启动框架配置文件、启动参数的配置,还需要在每个组件对应的启动任务文件中,通过实现[StartupTask](../reference/apis-ability-kit/js-apis-app-appstartup-startupTask.md)来添加启动任务。其中,需要用到下面的两个方法:
- [init](../reference/apis-ability-kit/js-apis-app-appstartup-startupTask.md#startuptaskinit):启动任务初始化。当该任务依赖的启动任务全部执行完毕,即onDependencyCompleted完成调用后,才会执行init方法对该任务进行初始化。
- [onDependencyCompleted](../reference/apis-ability-kit/js-apis-app-appstartup-startupTask.md#startuptaskondependencycompleted):当前任务依赖的启动任务执行完成时,调用该方法。
下面以[startup_config.json](#定义启动框架配置文件)中的StartupTask_001.ets文件为例,示例代码如下。开发者需要分别为每个待初始化组件添加启动任务。
> **说明:**
>
> 由于StartupTask采用了[Sendable协议](../arkts-utils/arkts-sendable.md#sendable协议),在继承该接口时,必须添加Sendable注解。
```ts
import { StartupTask, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Sendable
export default class StartupTask_001 extends StartupTask {
constructor() {
super();
}
async init(context: common.AbilityStageContext) {
hilog.info(0x0000, 'testTag', 'StartupTask_001 init.');
return 'StartupTask_001';
}
onDependencyCompleted(dependence: string, result: Object): void {
hilog.info(0x0000, 'testTag', 'StartupTask_001 onDependencyCompleted, dependence: %{public}s, result: %{public}s',
dependence, JSON.stringify(result));
}
}
```
### (可选)修改启动模式
AppStartup分别提供了自动和手动两种方式来执行启动任务,默认采用自动模式。开发者可以根据需要修改为手动模式。
- 自动模式:当AbilityStage组件容器完成创建后,自动执行启动任务。
- 手动模式:在UIAbility完成创建后手动调用,来执行启动任务。对于某些使用频率不高的模块,不需要应用最开始启动时就进行初始化。开发者可以选择将该部分启动任务修改为手动模式,在应用启动完成后调用[startupManager.run](../reference/apis-ability-kit/js-apis-app-appstartup-startupManager.md#startupmanagerrun)方法来执行启动任务。
下面以UIAbility的onCreate生命周期中为例,介绍如何采用手动模式来启动任务,示例代码如下。
```ts
import { AbilityConstant, UIAbility, Want, startupManager } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
let startParams = ['StartupTask_005', 'StartupTask_006'];
try {
startupManager.run(startParams).then(() => {
console.log('StartupTest startupManager run then, startParams = ');
}).catch((error: BusinessError) => {
console.info("StartupTest promise catch error, error = " + JSON.stringify(error));
console.info("StartupTest promise catch error, startParams = "
+ JSON.stringify(startParams));
})
} catch (error) {
let errMsg = JSON.stringify(error);
let errCode: number = error.code;
console.log('Startup catch error , errCode= ' + errCode);
console.log('Startup catch error ,error= ' + errMsg);
}
}
// ...
}
```
开发者还可以在页面加载完成后,在页面中调用启动框架手动模式,示例代码如下。
```ts
import { startupManager } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
@State message: string = '手动模式';
@State startParams: Array = ['StartupTask_006'];
build() {
RelativeContainer() {
Button(this.message)
.id('AppStartup')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
if (!startupManager.isStartupTaskInitialized("StartupTask_006")) { // 判断是否已经完成初始化
startupManager.run(this.startParams)
}
})
.alignRules({
center: {anchor: '__container__', align: VerticalAlign.Center},
middle: {anchor: '__container__', align: HorizontalAlign.Center}
})
}
.height('100%')
.width('100%')
}
}
```