HarmonyOS Next应用开发:UIAbility组件介绍
UIAbility是Stage模型中的组件类型名,包含UI,提供展示UI的能力,主要用于和用户交互。
UIAbility的设计理念:
1.原生支持应用组件级的跨端迁移和多端协同。
2.支持多设备和多窗口形态。
UIAbility划分原则与建议:
UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。
每一个UIAbility组件实例都会在最近任务列表中显示一个对应的任务。
对于开发者而言,可以根据具体场景选择单个还是多个UIAbility,划分建议如下:
如果开发者希望在任务视图中看到一个任务,则建议使用一个UIAbility,多个页面的方式。
如果开发者希望在任务视图中看到多个任务,或者需要同时开启多个窗口,则建议使用多个UIAbility开发不同的模块功能。
UIAbility组件基本用法
创建一个UIAbility需要继承UIAbility,并且UIAbility在启动过程中,需要指定启动页面,否则应用启动后会因为没有默认加载页面而导致白屏。可以在UIAbility的onWindowStageCreate()生命周期回调中,通过WindowStage对象的loadContent()方法设置启动页面。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err, data) => {
});
}
}
为使应用能够正常使用UIAbility,需要在module.json5配置文件的abilities标签中声明UIAbility的名称、入口、标签等相关信息。
"abilities": [
{
"name": "EntryAbility",// UIAbility组件的名称
"srcEntry": "./ets/entryability/EntryAbility.ts",// UIAbility组件的代码路径
"description": "$string:EntryAbility_desc",// UIAbility组件的描述信息
"icon": "$media:icon",// UIAbility组件的图标
"label": "$string:EntryAbility_label",// UIAbility组件的标签
"startWindowIcon": "$media:icon",// UIAbility组件启动页面图标资源文件的索引
"startWindowBackground": "$color:start_window_background",// UIAbility组件启动页面背景颜色资源文件的索引
"exported": true,// UIAbility组件是否可以被其他应用调用
"skills": [// UIAbility组件能够接收的Want特征集,为数组格式
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
Stage模型支持对组件配置入口图标和入口标签。入口图标和入口标签会显示在桌面上。
入口图标需要在 module.json5配置文件中配置,除了需要配置icon与label字段,还需要在skills标签下面的entities中添加"entity.system.home"、actions中添加"action.system.home"。同一个应用有多个UIAbility配置上述字段时,桌面上会显示出多个图标,分别对应各自的UIAbility。
如果abilities标签下声明的UIAbility的skills标签都为空,那么此应用为无图标应用,系统对无图标应用实施严格管控,防止一些恶意应用故意配置无桌面应用图标,导致用户找不到软件所在的位置,无法操作卸载应用,在一定程度上保证用户终端设备的安全。除预置应用外,其他应用不支持隐藏桌面图标。
UIAbility间的跳转和数据传递
启动应用内的UIAbility
创建两个UIAbility,分别为EntryAbility和SecondAbility。在EntryAbility下的页面Index中获取UIAbility实例的上下文信息,通过UIAbilityContext的startAbility()方法启动UIAbility,startAbility()方法中的Want携带了启动Ability的信息。
import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)
const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('点击')
.margin({
top: 20
})
.width('50%')
.height('5%')
.onClick(()=>{
// context为Ability对象的成员,在非Ability对象内部调用需要
let context = getContext(this) as common.UIAbilityContext;
// 将Context对象传递过去
let wantInfo: Want = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.loop.hello',
moduleName: 'entry', // moduleName非必选
abilityName: 'SecondAbility',
parameters: {
// 自定义信息
info: '来自EntryAbility Index页面'
},
};
// context为调用方UIAbility的UIAbilityContext
context.startAbility(wantInfo).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
}).catch((error) => {
hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
});
})
}
.width('100%')
}
.height('100%')
}
}
在SecondAbility的onCreate()或者onNewWant()生命周期回调文件中接收EntryAbility传递过来的参数。
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)
export default class SecondAbility extends UIAbility {
onCreate(want, launchParam) {
// 接收调用方UIAbility传过来的参数]
let info = want?.parameters?.info;
hilog.info(0x0000, 'SecondAbility', '%{public}s', `want param: ${info}`);
}
}
启动应用内的UIAbility并获取返回结果
在一个EntryAbility启动另外一个SecondAbility时,希望在被启动的SecondAbility完成相关业务后,能将结果返回给调用方。在EntryAbility中,调用startAbilityForResult()接口启动SecondAbility,异步回调中的data用于接收SecondAbility停止自身后返回给EntryAbility的信息。
import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction';
const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('点击')
.margin({
top: 20
})
.width('50%')
.height('5%')
.onClick(()=>{
// context为Ability对象的成员,在非Ability对象内部调用需要
let context = getContext(this) as common.UIAbilityContext;
const RESULT_CODE: number = 1001;
// 将Context对象传递过去
let wantInfo: Want = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.loop.hello',
moduleName: 'entry', // moduleName非必选
abilityName: 'SecondAbility',
parameters: {
// 自定义信息
info: '来自EntryAbility Index页面'
},
};
// context为调用方UIAbility的UIAbilityContext
context.startAbilityForResult(wantInfo).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, 'startAbility success.');
}).catch((error) => {
hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
});
})
}
.width('100%')
}
.height('100%')
}
}
在SecondAbility停止自身时,需要调用terminateSelfWithResult()方法,入参abilityResult为SecondAbility需要返回给EntryAbility的信息。
import common from '@ohos.app.ability.common'
import hilog from '@ohos.hilog'
const TAG: string = 'SecondPage';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct SecondPage {
@State message: string = 'SecondPage'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.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.loop.hello',
moduleName: 'entry', // moduleName非必选
abilityName: 'EntryAbility',
parameters: {
info: '来自SecondAbility SecondPage页面'
},
},
};
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;
}
});
})
}
.width('100%')
}
.height('100%')
}
}
FuncAbility停止自身后,EntryAbility通过startAbilityForResult()方法回调接收被FuncAbility返回的信息,RESULT_CODE需要与前面的数值保持一致。
启动UIAbility的指定页面
一个UIAbility可以对应多个页面,在不同的场景下启动该UIAbility时需要展示不同的页面,例如从一个UIAbility的页面中跳转到另外一个UIAbility时,希望启动目标UIAbility的指定页面。
UIAbility的启动分为两种情况:UIAbility冷启动和UIAbility热启动。
UIAbility冷启动:指的是UIAbility实例处于完全关闭状态下被启动,这需要完整地加载和初始化UIAbility实例的代码、资源等。
UIAbility热启动:指的是UIAbility实例已经启动并在前台运行过,由于某些原因切换到后台,再次启动该UIAbility实例,这种情况下可以快速恢复UIAbility实例的状态。
调用方UIAbility指定启动页面
调用方UIAbility启动另外一个UIAbility时,通常需要跳转到指定的页面,此时需要在传入的want参数中配置指定的页面路径信息,可以通过want中的parameters参数增加一个自定义参数传递页面跳转信息。
import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import hilog from [<u>'@ohos.hilog';</u>](mailto:'@ohos.hilog';)
const TAG: string = 'IndexPage';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('点击')
.margin({
top: 20
})
.width('50%')
.height('5%')
.onClick(()=>{
// context为Ability对象的成员,在非Ability对象内部调用需要
let context = getContext(this) as common.UIAbilityContext;
// 将Context对象传递过去
let wantInfo: Want = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.loop.hello',
moduleName: 'entry', // moduleName非必选
abilityName: 'SecondAbility',
parameters: {
// 自定义参数传递页面信息
router: 'SecondAnotherPage'
},
};
// context为调用方UIAbility的UIAbilityContext
context.startAbility(wantInfo).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
}).catch((error) => {
hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
});
})
}
.width('100%')
}
.height('100%')
}
}
目标UIAbility冷启动
目标UIAbility冷启动时,在目标UIAbility的onCreate()生命周期回调中,接收调用方传过来的参数。然后在目标UIAbility的onWindowStageCreate()生命周期回调中,解析EntryAbility传递过来的want参数,获取到需要加载的页面信息url,传入windowStage.loadContent()方法。
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import router from '@ohos.router';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[SecondAbility]';
export default class SecondAbility extends UIAbility {
secondAbilityWant : Want|undefined = undefined;
onCreate(want, launchParam) {
// 接收调用方UIAbility传过来的参数]
this.secondAbilityWant = want;
}
onWindowStageCreate(windowStage: window.WindowStage) {
let url = 'pages/SecondPage';
if(this.secondAbilityWant?.parameters?.router && this.secondAbilityWant.parameters.router === 'SecondAnotherPage') {
url = 'pages/SecondAnotherPage';
}
windowStage.loadContent(url, (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
}
目标UIAbility热启动
在应用开发中,会遇到目标UIAbility实例之前已经启动过的场景,这时再次启动目标UIAbility时,不会重新走初始化逻辑,只会直接触发onNewWant()生命周期方法。为了实现跳转到指定页面,需要在onNewWant()中解析参数进行处理。这里通过在SecondAbility的SecondAnotherPage启动SecondAbility的SecondPage来模拟热启动。
import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want'
import hilog from '@ohos.hilog'
const TAG: string = 'SecondAnotherPage';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct SecondAnotherPage {
@State message: string = 'SecondAnotherPage'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
let context = getContext(this) as common.UIAbilityContext;
// 将Context对象传递过去
let wantInfo: Want = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.loop.hello',
moduleName: 'entry', // moduleName非必选
abilityName: 'SecondAbility',
parameters: {
// 自定义信息
router: 'SecondPage'
},
};
// context为调用方UIAbility的UIAbilityContext
context.startAbility(wantInfo).then(()=>{
hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
}).catch((error) => {
hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
});
})
}
.width('100%')
}
.height('100%')
}
}
在SecondAbility的onNewWant()回调中(而不会再走onCreate()和onWindowStageCreate()等初始化逻辑)解析调用方传递过来的want参数,并进行指定页面的跳转。此时再次启动该SecondAbility实例时,即可跳转到该UIAbility实例的指定页面。
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import router from '@ohos.router';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[SecondAbility]';
export default class SecondAbility extends UIAbility {
secondAbilityWant : Want|undefined = undefined;
onCreate(want, launchParam) {
// 接收调用方UIAbility传过来的参数]
this.secondAbilityWant = want;
}
onWindowStageCreate(windowStage: window.WindowStage) {
let url = 'pages/SecondPage';
if(this.secondAbilityWant?.parameters?.router && this.secondAbilityWant.parameters.router === 'SecondAnotherPage') {
url = 'pages/SecondAnotherPage';
}
windowStage.loadContent(url, (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onNewWant');
if (want?.parameters?.router && want.parameters.router === 'SecondPage') {
let url = 'pages/SecondPage';
router.pushUrl({
url: url
}).catch((err) => {
hilog.error(DOMAIN_NUMBER, TAG, `Failed to push url. Code is ${err.code}, message is ${err.message}`);
});
}
}
}
当被调用方UIAbility组件启动模式设置为multiton启动模式时,每次启动都会创建一个新的实例,那么onNewWant()回调就不会被用到。