Harmony os------启动应用内的 UIAbility:跨 Ability 跳转、回传结果 & 指定页面全流程
这篇是我给自己整理的 「应用内 UIAbility 跳转 & 通信」实战笔记,配合 Stage 模型一起看会更清晰。 重点搞清楚三件事:
- 如何在应用内启动另一个 UIAbility
- 如何 拿返回结果 / 返回结果给调用方
- 如何在启动时 直接打开目标 UIAbility 的指定页面
一、先把场景想清楚:为什么要「应用内启动 UIAbility」
典型场景:
-
支付 App
EntryAbility:入口 / 首页FuncAbilityA:收付款 / 具体功能 → 从入口页面点击按钮,拉起另一个 UIAbility
-
登录场景
EntryAbility:主功能LoginAbility:专门做账号登录,登录完成后把结果返回给 EntryAbility
-
多页面能力
- 一个 UIAbility 对应多个页面(
Index、Second等),希望根据不同入口跳到不同页面
- 一个 UIAbility 对应多个页面(
下面分块记录我自己的理解 + 常用写法。
二、应用内启动 UIAbility(只要跳转,不关心返回)
从
EntryAbility的一个页面,启动同应用内的FuncAbilityA。
2.1 调用方:在页面中通过 startAbility() 启动目标 UIAbility
typescript
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 = this.getUIContext().getHostContext() as common.UIAbilityContext;
build() {
Column() {
List({ initialIndex: 0 }) {
ListItem() {
Row() {
// ... UI 展示
}
.onClick(() => {
const wantInfo: Want = {
deviceId: '', // 为空 = 本设备
bundleName: 'com.samples.stagemodelabilitydevelop',
moduleName: 'entry', // 同一个 module 可省略
abilityName: 'FuncAbilityA',
parameters: {
info: '来自 EntryAbility Page_UIAbilityComponentsInteractive 页面'
},
};
this.context.startAbility(wantInfo)
.then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
})
.catch((error: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG, `startAbility failed: ${error.code}, ${error.message}`);
});
})
}
}
}
}
}
要点:
bundleName:目标应用的包名(这里是自己)abilityName:目标 UIAbility 名moduleName:目标 UIAbility 在其他 module 时要写parameters:自定义透传参数,目标 UIAbility 在want.parameters中读取
2.2 被调方:在 FuncAbilityA 中接收参数
scala
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
export default class FuncAbilityA extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 接收调用方传过来的参数
const info = want?.parameters?.info;
console.info(`来自调用方的 info: ${info}`);
}
}
提示:
want.parameters里不仅有你自己塞进去的参数,系统也会自动塞入调用方的 PID、BundleName 等信息,可以用来做简单的"来源识别"。
2.3 被调方业务完成后,结束当前 UIAbility
比如 FuncAbilityB 用完就可以关掉:
typescript
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('关闭当前 UIAbility')
.onClick(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
context.terminateSelf((err) => {
if (err.code) {
hilog.error(
DOMAIN_NUMBER,
TAG,
`Failed to terminate self. Code: ${err.code}, message: ${err.message}`
);
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'terminateSelf success.');
});
})
}
}
}
⚠️ 注意:
默认
terminateSelf()后,这个 Ability 的任务快照还会留在「最近任务」里。如果不想留快照,可以在
module.json5里把对应 UIAbility 的:
json"removeMissionAfterTerminate": true打开。
如果是「我要直接关闭整个应用」,可以用ApplicationContext.killAllProcesses()把所有进程都杀掉。
三、应用内启动 UIAbility并获取返回结果(startActivityForResult 版能力)
这个场景很常见,比如:
EntryAbility→ 进入LoginAbility做账号登录;- 登录成功 / 失败之后,
LoginAbility把结果返回给EntryAbility。
3.1 调用方:使用 startAbilityForResult()
typescript
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 {
build() {
Column() {
List({ initialIndex: 0 }) {
ListItem() {
Row() {
// ...
}
.onClick(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const RESULT_CODE = 1001;
const want: Want = {
deviceId: '',
bundleName: 'com.samples.stagemodelabilitydevelop',
moduleName: 'entry',
abilityName: 'FuncAbilityA',
parameters: {
info: '来自 EntryAbility UIAbilityComponentsInteractive 页面'
}
};
context.startAbilityForResult(want)
.then((data) => {
// data: AbilityResult
if (data?.resultCode === RESULT_CODE) {
const info = data.want?.parameters?.info;
hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? '');
if (info !== null) {
this.getUIContext().getPromptAction().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: ${err.code}, message: ${err.message}`
);
});
})
}
}
}
}
}
这里有几个关键点:
- 用的是
startAbilityForResult(),不是startAbility(); RESULT_CODE需要和被调方返回时约定好;- 结果通过
.then((data) => { ... })里的data拿到,类型是AbilityResult。
3.2 被调方:用 terminateSelfWithResult() 带结果结束
typescript
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(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const RESULT_CODE = 1001;
const abilityResult: common.AbilityResult = {
resultCode: RESULT_CODE,
want: {
bundleName: 'com.samples.stagemodelabilitydevelop',
moduleName: 'entry',
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: ${err.code}, message: ${err.message}`
);
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'terminateSelfWithResult success.');
});
})
}
}
}
}
}
小结一下这一块:
- 调用方:
startAbilityForResult(want)→ 拿 Promise 结果;- 被调方:
terminateSelfWithResult(abilityResult)→ 把结果丢回去;- 用
resultCode做"是哪种结果"的标记,用want.parameters塞具体数据。
四、启动 UIAbility 的指定页面(指定首页 / 详情页)
一个 UIAbility 可以对应多个页面(比如 Index, Page_ColdStartUp, PageOne 等),我们经常会有需求:
「从 A 跳到 B 时,希望 B 打开某个指定页面」
这里拆成两种情况:
- 目标 UIAbility 冷启动(之前没起来过)
- 目标 UIAbility 热启动(之前已经启动过,只是现在在后台)
4.1 调用方:通过 Want.parameters 指定跳转目标
调用方约定一个字段,比如 router:
typescript
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 {
build() {
Column() {
List({ initialIndex: 0 }) {
ListItem() {
Row() {
// ...
}
.onClick(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const want: Want = {
deviceId: '',
bundleName: 'com.samples.stagemodelabilityinteraction',
moduleName: 'entry',
abilityName: 'FuncAbility',
parameters: {
router: 'funcA' // 自定义页面标识
}
};
context.startAbility(want)
.then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ability.');
})
.catch((err: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG,
`Failed to start ability. Code: ${err.code}, message: ${err.message}`);
});
})
}
}
}
}
}
4.2 目标 UIAbility 冷启动:在 onCreate + onWindowStageCreate 中处理
思路:
onCreate()里把整个want先存起来;onWindowStageCreate()里根据want.parameters.router决定loadContent哪个页面。
typescript
import { AbilityConstant, Want, UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window, UIContext } from '@kit.ArkUI';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[EntryAbility]';
export default class EntryAbility extends UIAbility {
funcAbilityWant: Want | undefined = undefined;
uiContext: UIContext | undefined = undefined;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 接收调用方传来的参数
this.funcAbilityWant = want;
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN_NUMBER, TAG, 'Ability onWindowStageCreate');
// 默认首页
let url = 'pages/Index';
// 根据 router 决定要加载哪个页面
if (this.funcAbilityWant?.parameters?.router === 'funcA') {
url = 'pages/Page_ColdStartUp';
}
windowStage.loadContent(url, (err, data) => {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG, `loadContent failed: ${err.code}, ${err.message}`);
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'loadContent success.');
});
}
}
4.3 目标 UIAbility 热启动:利用 onNewWant + AppStorage + Navigation
热启动时特点:
- UIAbility 实例已经存在;
- 不会再走
onCreate()和onWindowStageCreate(); - 只会触发
onNewWant()。
做法:
- 冷启动时,在
onWindowStageCreate()里拿到UIContext/Navigation所在的上下文; - 热启动时,在
onNewWant()中通过AppStorage写入一个"要跳到哪个页面"的标记; - 在首页页面(
Index.ets)的onPageShow()中读取这个标记,然后通过Navigation做页面跳转。
① 冷启动时获取 UIContext(可选步骤,但一般会一起做)
typescript
import { Want, UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window, UIContext } from '@kit.ArkUI';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[EntryAbility]';
export default class EntryAbility extends UIAbility {
funcAbilityWant: Want | undefined = undefined;
uiContext: UIContext | undefined = undefined;
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN_NUMBER, TAG, 'Ability onWindowStageCreate');
let url = 'pages/Index';
if (this.funcAbilityWant?.parameters?.router === 'funcA') {
url = 'pages/Page_ColdStartUp';
}
windowStage.loadContent(url, (err, data) => {
if (err.code) {
return;
}
let windowClass: window.Window;
windowStage.getMainWindow((err, data) => {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG,
`Failed to obtain the main window. Code: ${err.code}, message: ${err.message}`);
return;
}
windowClass = data;
this.uiContext = windowClass.getUIContext();
});
});
}
}
② 热启动:在 onNewWant() 中设置全局变量
typescript
import { AbilityConstant, Want, UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN_NUMBER: number = 0xFF00;
const TAG: string = '[EntryAbility]';
export default class EntryAbility extends UIAbility {
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant');
// 用 AppStorage 通知页面「下一次显示时要跳到某个页面」
AppStorage.setOrCreate<string>('nameForNavi', 'pageOne');
}
}
③ 在首页 Index.ets 中根据标记做跳转(Navigation)
scss
@Entry
@Component
struct Index {
@State message: string = 'Index';
pathStack: NavPathStack = new NavPathStack();
onPageShow(): void {
const somePage = AppStorage.get<string>('nameForNavi');
if (somePage) {
this.pathStack.pushPath({ name: somePage }, false);
AppStorage.delete('nameForNavi'); // 用完就删,避免下次误跳
}
}
build() {
Navigation(this.pathStack) {
Text(this.message)
.id('Index')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
}
.mode(NavigationMode.Stack)
.height('100%')
.width('100%')
.margin({ top: 250 })
}
}
④ 子页面 PageOne.ets 示例
scss
@Builder
export function PageOneBuilder() {
PageOne();
}
@Component
export struct PageOne {
@State message: string = 'PageOne';
pathStack: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Text(this.message)
.id('PageOne')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
}
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack;
})
.height('100%')
.width('100%')
.margin({ top: 250 })
}
}
⑤ 路由表 & module.json5 配置
route_map.json:
json
{
"routerMap": [
{
"name": "pageOne",
"pageSourceFile": "src/main/ets/pages/PageOne.ets",
"buildFunction": "PageOneBuilder",
"data": {
"description": "this is pageOne"
}
}
]
}
module.json5 中挂载:
kotlin
{
"module": {
// ...
"routerMap": "$profile:route_map"
}
}
注意:
- 如果目标 UIAbility 的启动模式是 multiton ,每次都是新实例 → 不会触发
onNewWant(),而是重新走onCreate()+onWindowStageCreate(),此时就走冷启动那套逻辑即可。onNewWant()主要用在 singleton / specified 模式下的"复用已有实例"场景。
五、最后小结一张脑图式记忆
可以这样记住整套逻辑:
-
在应用内启动其他 UIAbility
- 不要结果 →
startAbility() - 要结果 →
startAbilityForResult() + terminateSelfWithResult()
- 不要结果 →
-
被调方 Ability 怎么写
- 参数:
onCreate(want)/onNewWant(want)里从want.parameters取 - 结束:
terminateSelf()/terminateSelfWithResult()
- 参数:
-
指定页面启动
- 调用方:
want.parameters.router = 'xxx' - 冷启动:
onCreate()存 want →onWindowStageCreate()里决定loadContent(url) - 热启动:
onNewWant()→AppStorage.set('nameForNavi', 'pageOne')→ 首页onPageShow()里
- 调用方: