鸿蒙UIAbility组件进阶
AbilityStage
基本概念
在了解AbilityStage之前,我们需要先了解和回顾几个概念,UIAbility、ExtensionAbility、HAP。
UIAbility
:前边的文章中我们讲过,UIAbility是包含UI的应用组件,主要用于和用户交互。ExtensionAbility
:是一种面向特定场景的应用组件,如输入法、桌面卡片、闲时任务、数据备份等,每一个具体场景对应一种ExtensionAbilityType,只能只用系统已经定义的场景。所有的ExtensionAbility组件均不能被应用直接启动,而是由响应的系统管理服务拉起,以确保其生命周期受系统管控。HAP
:应用安装的基本单位,一个应用会包含一个或者多个.hap文件,.hap是由项目工程中的module编译而成的。
回顾了几个概念之后,我们来说今天的AbilityStage。
AbilityState
是Module级别的容器组件,与HAP是一一对应的关系。
当HAP中的代码首次被加载到应用进程中时,也就是Module初始化的时候,系统首先会创建一个AbilityState实例,AbilityStage可以管理Module中的Ability组件和ExtensionAbility组件。
关系图如下:
一个应用,也就是application,可以包含一个或者多个HAP,每个HAP对应一个AbilityStage,每个AlibityStage可以管理一个或者多个UIAbility,每个UIAbility中包含一个WindowStage,用于管理Window,每个Window中,可以管理一个或者多个UI页面。
当我们需要打开某一个UIAbility时:
- 如果该UIAbility所在的HAP还没有加载到进程中,那么系统会将HAP加载到进程中,创建一个AbilityStage实例。创建成功后,执行AbilityStage的onCreate生命周期函数,可以在该方法中进行一些module级别的资源初始化。
- 创建UIAbility实例,创建成功后,回调UIAbility的onCreate生命周期函数,可以在该回调函数中初始化一些业务逻辑。
- 系统为该UIAbility实例创建一个WindowStage实例,创建完成后,会回调UIAbility的onWindowStageCreate生命周期回调函数,可以在该回调函数中获取主窗口信息,设置UI界面以及监听WindowStage的生命周期变化。
如下图所示:
在鸿蒙中,应用组件和UI之间的生命周期是松耦合的,在这种设计模式下,有几个好处
-
业务逻辑与UI分离,我们可以在UIAbility中处理一些与UI无关的业务逻辑,如打开蓝牙,连接数据库等。而在WindowStage持有的窗口上去处理界面相关的业务逻辑。
-
便于系统对应用组件进行裁剪。比如无屏设备,系统在创建时不会创建窗口模块,可以减少运行内存占用。
-
在多设备上,应用组件可以使用同一套生命周期。
单窗口模式:
这个流程会触发App1中WindowStage的
hidden
和inActive
生命周期,触发UIAbility中的background
生命周期;同时触发App2中WindowStage的shown
和active
生命周期,触发UIAbility中的foreground
生命周期。多窗口模式:
这个流程只会触发App1中WindowStage的
inActive
生命周期函数,同时触发App2中WindowStage的active
生命周期函数。而不会触发两个app中各自UIAbility中的生命周期函数。
生命周期
AbilityStage共有四个生命周期函数
- onCreate:AbilityStage实例创建完成后回调的生命周期函数,可以在这里进行Module的初始化操作。
- onAcceptWant:UIAbility指定实例模式启动时,会触发该回调,在该回调中,我们需要返回一个唯一的key值,系统用于判断指定的实例是否已经存在。
- onConfigurationUpdated:系统全局配置发生改变时触发的回调函数,如系统语言,深浅色等。可以在该函数中接收到系统最新的配置信息。
- onMemoryLevel:系统调整内存时触发的函数。
使用方式
-
在工程Module对应的ets目录下,右键选择"New > Directory",新建一个目录并命名为myabilitystage。
-
在myabilitystage目录,右键选择"New > ArkTS File",新建一个文件并命名为MyAbilityStage.ts。
-
打开MyAbilityStage.ts文件,导入AbilityStage的依赖包,自定义类继承AbilityStage并加上需要的生命周期回调,示例中增加了一个onCreate()生命周期回调。
import { AbilityStage, Want } from '@kit.AbilityKit'; export default class MyAbilityStage extends AbilityStage { onCreate(): void { // 应用的HAP在首次加载的时,为该Module初始化操作 } onAcceptWant(want: Want): string { // 仅specified模式下触发 return 'MyAbilityStage'; } }
-
在module.json5配置文件中,通过配置 srcEntry 参数来指定模块对应的代码路径,以作为HAP加载的入口。
{ "module": { "name": "entry", "type": "entry", "srcEntry": "./ets/myabilitystage/MyAbilityStage.ts", // ... } }
UIAbility组件间交互
UIAbility是系统调度的最小单元。在设备内的功能模块之间跳转时,会涉及到启动特定的UIAbility,包括应用内的其他UIAbility、或者其他应用的UIAbility(例如启动三方支付UIAbility)。
启动应用内的UIAbility
假设应用中有两个UIAbility:EntryAbility和FuncAbility(可以在同一个Module中,也可以在不同的Module中),需要从EntryAbility的页面中启动FuncAbility。
-
在EntryAbility中,通过调用startAbility()方法启动UIAbility,want为UIAbility实例启动的入口参数,其中bundleName为待启动应用的Bundle名称,abilityName为待启动的Ability名称,moduleName在待启动的UIAbility属于不同的Module时添加,parameters为自定义信息参数。
import { common, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { BusinessError } from '@kit.BasicServicesKit'; const TAG: string = '[Page_UIAbilityComponentsInteractive]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_UIAbilityComponentsInteractive { private context = getContext(this) as common.UIAbilityContext; build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() => { // context为Ability对象的成员,在非Ability对象内部调用需要 // 将Context对象传递过去 let wantInfo: Want = { deviceId: '', // deviceId为空表示本设备 bundleName: 'com.samples.stagemodelabilitydevelop', moduleName: 'entry', // moduleName非必选 abilityName: 'FuncAbilityA', parameters: { // 自定义信息 info: '来自EntryAbility Page_UIAbilityComponentsInteractive页面' }, }; // context为调用方UIAbility的UIAbilityContext this.context.startAbility(wantInfo).then(() => { hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.'); }).catch((error: BusinessError) => { hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.'); }); }) } //... } //... } //... } }
-
在FuncAbility的onCreate()或者onNewWant()生命周期回调文件中接收EntryAbility传递过来的参数。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; export default class FuncAbilityA extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // 接收调用方UIAbility传过来的参数 let funcAbilityWant = want; let info = funcAbilityWant?.parameters?.info; } //... }
-
在FuncAbility业务完成之后,如需要停止当前UIAbility实例,在FuncAbility中通过调用terminateSelf()方法实现。
import { common } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; const TAG: string = '[Page_FromStageModel]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_FromStageModel { build() { Column() { //... Button('FuncAbilityB') .onClick(() => { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext // context为需要停止的UIAbility实例的AbilityContext context.terminateSelf((err) => { if (err.code) { hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`); return; } }); }) } //... } }
调用terminateSelf()方法停止当前UIAbility实例时,默认会保留该实例的快照(Snapshot),即在最近任务列表中仍然能查看到该实例对应的任务。如不需要保留该实例的快照,可以在其对应UIAbility的module.json5配置文件中,将abilities标签的removeMissionAfterTerminate字段配置为true。
- 如需要关闭应用所有的UIAbility实例,可以调用ApplicationContext的killAllProcesses()方法实现关闭应用所有的进程。
启动应用内的UIAbility并获取返回结果
在一个EntryAbility启动另外一个FuncAbility时,希望在被启动的FuncAbility完成相关业务后,能将结果返回给调用方。例如在应用中将入口功能和账号登录功能分别设计为两个独立的UIAbility,在账号登录UIAbility中完成登录操作后,需要将登录的结果返回给入口UIAbility。
-
在EntryAbility中,调用startAbilityForResult()接口启动FuncAbility,异步回调中的data用于接收FuncAbility停止自身后返回给EntryAbility的信息。
import { common, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { promptAction } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; const TAG: string = '[Page_UIAbilityComponentsInteractive]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_UIAbilityComponentsInteractive { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() => { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext const RESULT_CODE: number = 1001; let want: Want = { deviceId: '', // deviceId为空表示本设备 bundleName: 'com.samples.stagemodelabilitydevelop', moduleName: 'entry', // moduleName非必选 abilityName: 'FuncAbilityA', parameters: { // 自定义信息 info: '来自EntryAbility UIAbilityComponentsInteractive页面' } }; context.startAbilityForResult(want).then((data) => { if (data?.resultCode === RESULT_CODE) { // 解析被调用方UIAbility返回的信息 let info = data.want?.parameters?.info; hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? ''); if (info !== null) { promptAction.showToast({ message: JSON.stringify(info) }); } } hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(data.resultCode) ?? ''); }).catch((err: BusinessError) => { hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`); }); }) } //... } //... } //... } }
-
在FuncAbility停止自身时,需要调用terminateSelfWithResult()方法,入参abilityResult为FuncAbility需要返回给EntryAbility的信息。
import { common } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; const TAG: string = '[Page_FuncAbilityA]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_FuncAbilityA { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() => { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext const RESULT_CODE: number = 1001; let abilityResult: common.AbilityResult = { resultCode: RESULT_CODE, want: { bundleName: 'com.samples.stagemodelabilitydevelop', moduleName: 'entry', // moduleName非必选 abilityName: 'FuncAbilityB', parameters: { info: '来自FuncAbility Index页面' }, }, }; context.terminateSelfWithResult(abilityResult, (err) => { if (err.code) { hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self with result. Code is ${err.code}, message is ${err.message}`); return; } }); }) } //... } //... } //... } }
-
FuncAbility停止自身后,EntryAbility通过startAbilityForResult()方法回调接收被FuncAbility返回的信息,RESULT_CODE需要与前面的数值保持一致。
import { common, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { promptAction } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; const TAG: string = '[Page_UIAbilityComponentsInteractive]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_UIAbilityComponentsInteractive { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() => { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext const RESULT_CODE: number = 1001; let want: Want = { deviceId: '', // deviceId为空表示本设备 bundleName: 'com.samples.stagemodelabilitydevelop', moduleName: 'entry', // moduleName非必选 abilityName: 'FuncAbilityA', parameters: { // 自定义信息 info: '来自EntryAbility UIAbilityComponentsInteractive页面' } }; context.startAbilityForResult(want).then((data) => { if (data?.resultCode === RESULT_CODE) { // 解析被调用方UIAbility返回的信息 let info = data.want?.parameters?.info; hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? ''); if (info !== null) { promptAction.showToast({ message: JSON.stringify(info) }); } } hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(data.resultCode) ?? ''); }).catch((err: BusinessError) => { hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ability for result. Code is ${err.code}, message is ${err.message}`); }); }) } //... } //... } //... } }
启动UIAbility的指定页面
一个UIAbility可以对应多个页面,在不同的场景下启动该UIAbility时需要展示不同的页面,可以通过如下方式进行。
调用方
调用方UIAbility启动另外一个UIAbility时,通常需要跳转到指定的页面。此时需要在传入的want参数中配置指定的页面路径信息,可以通过want中的parameters参数增加一个自定义参数传递页面跳转信息。
目标UIAbility冷启动
目标UIAbility冷启动时,在目标UIAbility的onCreate()生命周期回调中,接收调用方传过来的参数。然后在目标UIAbility的onWindowStageCreate()生命周期回调中,解析EntryAbility传递过来的want参数,获取到需要加载的页面信息url,传入windowStage.loadContent()方法。
目标UIAbility热启动
在应用开发中,会遇到目标UIAbility实例之前已经启动过的场景,这时再次启动目标UIAbility时,不会重新走初始化逻辑,只会直接触发onNewWant()生命周期方法。为了实现跳转到指定页面,需要在onNewWant()中解析参数进行处理。