Harmony os——启动应用内的 UIAbility:跨 Ability 跳转、回传结果 & 指定页面全流程

Harmony os------启动应用内的 UIAbility:跨 Ability 跳转、回传结果 & 指定页面全流程

这篇是我给自己整理的 「应用内 UIAbility 跳转 & 通信」实战笔记,配合 Stage 模型一起看会更清晰。 重点搞清楚三件事:

  • 如何在应用内启动另一个 UIAbility
  • 如何 拿返回结果 / 返回结果给调用方
  • 如何在启动时 直接打开目标 UIAbility 的指定页面

一、先把场景想清楚:为什么要「应用内启动 UIAbility」

典型场景:

  • 支付 App

    • EntryAbility:入口 / 首页
    • FuncAbilityA:收付款 / 具体功能 → 从入口页面点击按钮,拉起另一个 UIAbility
  • 登录场景

    • EntryAbility:主功能
    • LoginAbility:专门做账号登录,登录完成后把结果返回给 EntryAbility
  • 多页面能力

    • 一个 UIAbility 对应多个页面(IndexSecond 等),希望根据不同入口跳到不同页面

下面分块记录我自己的理解 + 常用写法。


二、应用内启动 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 打开某个指定页面」

这里拆成两种情况:

  1. 目标 UIAbility 冷启动(之前没起来过)
  2. 目标 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 中处理

思路:

  1. onCreate() 里把整个 want 先存起来;
  2. 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.');
     });
   }
 }

热启动时特点:

  • UIAbility 实例已经存在;
  • 不会再走 onCreate()onWindowStageCreate()
  • 只会触发 onNewWant()

做法:

  1. 冷启动时,在 onWindowStageCreate() 里拿到 UIContext / Navigation 所在的上下文;
  2. 热启动时,在 onNewWant() 中通过 AppStorage 写入一个"要跳到哪个页面"的标记;
  3. 在首页页面(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 模式下的"复用已有实例"场景。

五、最后小结一张脑图式记忆

可以这样记住整套逻辑:

  1. 在应用内启动其他 UIAbility

    • 不要结果 → startAbility()
    • 要结果 → startAbilityForResult() + terminateSelfWithResult()
  2. 被调方 Ability 怎么写

    • 参数:onCreate(want) / onNewWant(want) 里从 want.parameters
    • 结束:terminateSelf() / terminateSelfWithResult()
  3. 指定页面启动

    • 调用方:want.parameters.router = 'xxx'
    • 冷启动:onCreate() 存 want → onWindowStageCreate() 里决定 loadContent(url)
    • 热启动:onNewWant()AppStorage.set('nameForNavi', 'pageOne') → 首页 onPageShow()
相关推荐
用户463989754321 小时前
Harmony os——UIAbility 组件生命周期|我按自己的理解梳了一遍
harmonyos
汉堡黄2 小时前
鸿蒙开发:案例集合Tabs:自定义tabs突出(凸出)球体左右跟随滑动动画
harmonyos
Q***l6872 小时前
HarmonyOS在智能穿戴中的Huawei Watch
华为·harmonyos
p***43487 小时前
HarmonyOS系统架构
华为·系统架构·harmonyos
Y***K43410 小时前
HarmonyOS在智能穿戴中的健康算法
华为·harmonyos
1***815313 小时前
HarmonyOS在智能车载中的娱乐系统
华为·harmonyos·娱乐
4***R24013 小时前
HarmonyOS在智能车载中的车载娱乐
华为·harmonyos·娱乐
食品一少年14 小时前
【DAY1】零基础Flutter 编译开发 鸿蒙HarmonyOS
华为·harmonyos
T***160714 小时前
HarmonyOS在智能家居中的应用
华为·智能家居·harmonyos