启动框架执行时机
在开始介绍 启动框架AppStartup
之前先看看启动框架执行的时机
可以看到在构造 AbilityStage组件容器
过程中开始加载开发者配置的启动任务,并执行自动模式的启动任务,AbilityStage
是一个Module
级别的组件容器,应用的HAP
在首次加载时会创建一个AbilityStage实例,可以对该Module进行初始化等操作。AbilityStage与Module一一对应,即一个Module拥有一个AbilityStage。和Android 的Application有相似的作用其中它们的回调方法也是类似的。而AbilityStage使用也是非常简单,在工程Module对应的ets目录下创建MyAbilityStage类并继承AbilityStage,然后在在 module.json5
配置文件中,通过配置 srcEntry
参数来指定模块对应的代码路径,以作为HAP加载的入口。
开发者也可以在UIAbility创建完后手动调用startupManager.run
方法,执行手动模式的启动任务。启动框架支持以自动模式或手动模式执行启动任务,默认采用自动模式。其实个人认为 自动启动模式
是有弊端的,目前各个应用市场应工信部要求必须要在应用首次启动展示隐私协议,所以自动启动模式
并不适用。所以startupManager.run
手动模式的启动任务比较合适当下的情况。比如:如上图应用启动过程中最好才到UIAbility,一般我们会在UIAbillity生命周期函数onWindowStageCreate中调用windowStage.loadContent函数加载指定Page,那么可能我们需要闪屏Page,这时就展示闪屏Page,并在闪屏Page中展示隐私协议,那么我们就在闪屏Page调用startupManager.run
手动启动任务。当然自动启动也不是用不到,如果有些任务不收集用户敏感信息那么可以让任务自动启动但是这时不可控的,使用手动启动任务我们可以控制启动时机,这种方式会更可控。
启动框架AppStartup
AppStartup 是HarmonyOS 提供了一种简单高效的应用启动方式, 可以支持多任务的多线程启动,
从而达到加快应用启动速度的目的. AppStartup 通过在一个配置文件中统一设置多个启动任务的执行线程,和执行顺序以及依赖关系,让执行启动任务的代码变得更加简洁清晰, 易于维护.
约束限制
- 启动框架只支持在entry类型的Module下的UIAbility中使用。
- 启动任务之间不允许存在循环依赖。
1、定义启动框架配置文件
-
在应用主模块(即entry类型的Module)的
resources/base/profile
路径下, 新建启动框架配置文件。 配置文件为 json 格式, 文件名可以自定义, 本文以startup_config.json为例。-
在启动框架配置文件startup_config.json中,依次添加各个启动任务的配置信息。
假设当前应用启动框架共包含6个启动任务,任务之间的依赖关系如下图所示。为了便于并发执行启动任务,单个启动任务文件包含的启动任务应尽量单一,本例中每个启动任务对应一个启动任务文件。
tasks.png
-
-
在ets/startup路径下, 依次创建6个启动任务文件, 以及一个公共的启动参数配置文件. 文件名称必须确保唯一性.
创建启动任务文件. 本例中的6个文件名分别为StartupTask_001~006.ets。
-
创建启动任务公共参数配置文件. 本例中的文件名为StartupConfig.ets。
在启动框架配置文件startup_config.json中, 添加所有启动任务以及启动参数配置文件的相关信息. startup_config.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" }
-
startupTasks 启动任务配置信息
- excludeFromAutoStart 是否排除自动模式, 详细介绍可以查看修改启动模式. - true: 手动模式. - false: 自动模式.
- runOnThread 执行初始化所在的线程. - mainThread: 在主线程中执行. - taskPool: 在异步线程中执行.
- waitOnMainThread 主线程是否需要等待启动框架执行. 当runOnThread取值为taskPool时, 该字段生效. - true: 主线程等待启动框架执行完之后, 才会加载应用首页. - false: 主线程不等待启动任务执行.
configEntry 启动参数配置文件所在路径.
然后在module.json5配置文件的appStartup标签中, 添加启动框架配置文件的索引. module.json5示例代码如下.
{
"module": {
"name": "entry",
"type": "entry",
// ...
"appStartup": "$profile:startup_config", // 启动框架的配置文件
// ...
}
}
设置启动参数
在启动参数配置文件(本文为ets/startup/StartupConfig.ets文件)中, 使用StartupConfigEntry接口实现启动框架公共参数的配置, 包括超时时间和启动任务的监听器等参数, 其中需要用到如下接口:
StartupConfig: 用于设置任务超时时间和启动框架的监听器.
-
StartupListener: 用于监听启动任务是否执行成功.
export default class MyStartupConfigEntry extends StartupConfigEntry { onConfig() { hilog.info(0x0000, 'testTag', `onConfig`); let onCompletedCallback = (error: BusinessError<void>) => { 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来添加启动任务. 其中, 需要用到下面的两个方法:
init: 启动任务初始化. 当该任务依赖的启动任务全部执行完毕, 即onDependencyCompleted完成调用后, 才会执行init
方法对该任务进行初始化.-
onDependencyCompleted: 当前任务依赖的启动任务执行完成时, 调用该方法.
下面以startup_config.json中的StartupTask_001.ets文件为例, 示例代码如下. 开发者需要分别为每个待初始化组件添加启动任务.@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)); } }
由于StartupTask采用了Sendable协议, 在继承该接口时, 必须添加Sendable注解.
修改启动模式
AppStartup分别提供了自动和手动两种方式来执行启动任务, 默认采用自动模式. 开发者可以根据需要修改为手动模式。
自动模式: 当AbilityStage组件容器完成创建后, 自动执行启动任务.
-
手动模式: 在UIAbility完成创建后手动调用, 来执行启动任务. 对于某些使用频率不高的模块, 不需要应用最开始启动时就进行初始化。
开发者可以选择将该部分启动任务修改为手动模式, 在应用启动完成后调用startupManager.run方法来执行启动任务。下面以UIAbility#onCreate生命周期中为例, 介绍如何采用手动模式来启动任务, 示例代码如下。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); } } // ... }
开发者还可以在页面加载完成后, 在页面中调用启动框架手动模式, 示例代码如下.
@Entry
@Component
struct Index {
@State message: string = '手动模式';
@State startParams: Array<string> = ['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%')
}
}