1.概述
在我们应用启动时,通常需要初始化一些启动任务,如果我们将所有的任务都放置在应用主模块(也就是entry类型的Module)它的UIAbility组件的onCreate生命周期中,那么任务只能在主线程中依次执行,这样就会影响应用的启动速度,当启动任务过多时,任务之间复杂的依赖关系还会使得代码难以维护。官方提供了一种简单高效的应用启动框架AppStartup。
2.应用启动流程
-
进程启动过程:
- 由系统服务通过孵化进程拉起应用主进程并创建出相应的运行环境。
- 应用再根据实际情况请求系统服务。
- 启动应用所需的其他进程。
注意:每个应用至多并存三类进程(主进程、Extension类进程、Render进程),应用中所有的进程都是由系统创建和管理的。
-
模块启动顺序:
- 首先拉起应用的入口模块,该模块加载时会创建一个AbilityStage实例
(这里我们可以理解为Android中的Application),可以对该模块进行初始化等操作。 - 模块初始完成后,会拉起相应的入口UIAbility(这里我们可以理解为Android中的MainActivity)。
- UIAbility加载完成后会生成一个WindowStage类实例并与之绑定(这里我们可以理解为Android中的PhoneWindow)。WindowStage发挥了应用进程内窗口管理器的作用,UIAbility通过它持有一个窗口,在该窗口上加载出首个ArkUI页面(这里我们可以理解为Android中的布局文件R.layout.xxx),从而在设备上呈现。
- 首先拉起应用的入口模块,该模块加载时会创建一个AbilityStage实例
-
启动流程总结
系统服务孵化应用进程,拉起应用主进程,其次拉起应用入口模块,此时就会实例化AbilityState实例,拉起应用的入口UIAbility,执行其生命周期,当onCreate执行完成后会执行onWindowStageCreate会创建WindowState实例与之绑定,它是应用进程的窗口管理器,可以加载ArkUI页面。
3.AppStartup介绍
它是一种简单高效的应用启动方式,可以支持任务的异步启动,加快应用启动速度。同时,通过在一个配置文件中统一设置多个启动任务的执行顺序以及依赖关系,让执行启动任务的代码变得更加简洁清晰、容易维护。
功能特性
- 支持任务的异步启动。
- 配置文件统一配置任务的执行顺序及依赖关系。
- 支持自动 / 手动 执行启动任务。
执行流程图
image.png
使用限制
- 启动框架只支持在entry类型的Module下的UIAbility中使用。
- 启动任务之间不允许存在循环依赖。
使用流程
1. 定义启动框架配置文件:在资源文件目录下创建启动框架配置文件、添加启动任务的配置信息,并在module.json5配置文件中引用。
-
- 在应用主模块(即entry类型的Module)的“resources/base/profile”路径下,新建启动框架配置文件。文件名可以自定义,本文以"startup_config.json"为例。
{ "startupTasks": [ { "name":"StartupTask_001", //启动任务名称 "srcEntry": "./ets/startup/StartupTask_001.ets", //启动任务对应的文件路径 "dependencies": [ //启动任务依赖的其他启动任务的类名数组 "StartupTask_002" ], "runOnThread": "taskPool", //执行初始化所在的线程,默认主线程,mainThread:在主线程中执行。- taskPool:在异步线程中执行 "waitOnMainThread": false //主线程是否需要等待启动框架执行 }, { "name":"StartupTask_002", "srcEntry": "./ets/startup/StartupTask_002.ets", "dependencies": [], "runOnThread": "taskPool", "waitOnMainThread": false }, { "name":"StartupTask_003", "srcEntry": "./ets/startup/StartupTask_003.ets", "dependencies": [], "excludeFromAutoStart": true, //设置启动模式,默认为false, true:手动模式, false:自动模式 "runOnThread": "taskPool", "waitOnMainThread": false } ], "configEntry": "./ets/startup/StartupConfig.ets" }
- 2.在启动框架配置文件startup_config.json中,依次添加各个启动任务的配置信息。
{ "module": { "name": "entry", "type": "entry", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", ... } ] "appStartup": "$profile:startup_config" //配置AppStartUp的启动项任务文件 } }
2. 设置启动参数:在启动参数文件中,设置超时时间和启动任务的监听器等参数。
-
- 创建启动参数配置文件,使用StartupConfigEntry接口实现启动框架公共参数的配置,包括超时时间和启动任务的监听器等参数,其中需要用到如下接口:
- StartupConfig:用于设置任务超时时间和启动框架的监听器。
- StartupListener:用于监听启动任务是否执行成功。
export default class MyStartupConfigEntry extends StartupConfigEntry{ onConfig(): StartupConfig { console.info('ych' , `onConfig`) //监听启动任务执行的回掉,成功/失败 let onCompletedCallback = (error: BusinessError<void>) => { console.info('ych' , `onCompletedCallback`) if (error) { console.error('ych' , `onCompletedCallback error: ${error.message}`) } else { console.info('ych' , `onCompletedCallback: success.`) } }; let startupListener: StartupListener = { 'onCompleted': onCompletedCallback }; // 任务参数配置 let config: StartupConfig = { 'timeoutMs': 5000, // 任务超时时间 'startupListener': startupListener // 启动任务监听器 }; return config; } }
3. 为每个待初始化组件添加启动任务:通过实现StartupTask接口,启动框架将会按顺序执行初始化流程。
注意:
* 1. 启动任务必须实现 StartupTask 类
* 2. 由于StartupTask采用了 Sendable 协议(线程间通信对象),在继承该接口时,必须添加Sendable注解。
* 3. 如果任务不是应用创建时必须初始化,那么我们可以使用手动初始化,直接调用
* 3.1.
* try {
* startupManager.run(["StartupTask_003"]).then(()=>{
* console.error('ych' , `StartupTask_003 启动完成`)
* }).catch((err:BusinessError)=>{
* console.error('ych' , `StartupTask_003 启动失败 ${err.message}`)
* })
* }catch (error){
* console.error('ych' , `StartupTask_003 启动失败 ${error.message}`)
* }
* 3.2.需要在resource/config/startup.json中配置该任务为非必须任务。也就是 "excludeFromAutoStart": true
@Sendable
export default class StartupTask_001 extends StartupTask{
constructor() {
super();
}
/**
* TODO: 启动任务初始化。当该任务依赖的启动任务全部执行完毕,即onDependencyCompleted完成调用后,才会执行init方法对该任务进行初始化。
* 总结:
* 1.如果该任务无依赖任务,则不会执行onDependencyCompleted方法,直接执行init方法。
* 2.如果该任务有依赖任务,则先执行onDependencyCompleted方法,再执行init方法。
*/
async init(context: common.AbilityStageContext){
this.myStartupTask_001()
return "StartupTask_001"
}
onDependencyCompleted(dependence: string, result: Object): void {
console.error('ych', `依赖的任务名称为:${dependence} --- 结果:${result.valueOf()}`,
dependence, JSON.stringify(result));
}
myStartupTask_001(){
console.error('ych' , `StartupTask_001开始初始化`)
}
}
使用注意:
- 1. 启动任务必须实现 StartupTask 类
- 由于StartupTask采用了 Sendable 协议(线程间通信对象),在继承该接口时,必须添加Sendable注解。
-
如果任务不是应用创建时必须启动,那么我们可以使用手动启动,直接调用
- 3.1.手动启动任务
try { startupManager.run(["StartupTask_001"]).then(()=>{ console.error('ych' , `StartupTask_001 启动完成`) }).catch((err:BusinessError)=>{ console.error('ych' , `StartupTask_001 启动失败 ${err.message}`) }) }catch (error){ console.error('ych' , `StartupTask_001 启动失败 ${error.message}`) }
- 3.2.需要在resource/config/startup.json中配置该任务为非必须任务。也就是 "excludeFromAutoStart": true
{ "startupTasks": [ { "name":"StartupTask_001", "srcEntry": "./ets/startup/StartupTask_001.ets", "dependencies": [], "runOnThread": "taskPool", "excludeFromAutoStart": true, //设置启动模式,默认为false, true:手动模式, false:自动模式 "waitOnMainThread": false }, "configEntry": "./ets/startup/StartupConfig.ets" }
-