实现热启动跳转至指定Navigation组件页面
========================

#### 问题现象

通过卡片热启动应用，如何跳转至指定的Navigation路由栈中的页面。

#### 背景知识

*   [组件导航（Navigation）](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-navigation-navigation)：主要用于实现页面间以及组件内部的页面跳转，支持在不同组件间传递跳转参数，提供灵活的跳转栈操作。
*   [@ohos.events.emitter模块](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-emitter)：提供事件发送和处理的能力，包括持续订阅、单次订阅、取消订阅和发送事件到事件队列。
*   [onNewWant](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onnewwant)：当已经启动的UIAbility实例再次被拉起时，系统会触发该回调。可在onNewWant内获取到对应want参数信息，得知将要跳转的页面。
*   [getAllPathName](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-navigation#getallpathname10)：获取栈中所有NavDestination页面的名称。可用来判断将要跳转的页面，是否已在栈内，从而进行入栈出栈操作。

#### 解决方案

*   **方案一**：作为Navigation的根页面，首页不会被销毁，且页面上含有NavPathStack变量，可以进行页面跳转操作。通过在页面生命周期函数中发布事件通知首页，进行页面跳转操作。热启动的示例代码如下：
    1.  热启动：在被拉起方UIAbility的[onNewWant](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onnewwant)方法中（若UIAbility文件无此方法，则自行增加即可），通过[EventHub.emit](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inner-application-eventhub#eventhubemit)触发事件
        
        this.context.eventHub.emit('navigation', want.parameters?.path?.toString());
        
    2.  在Navigation的根页面通过[EventHub.on](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inner-application-eventhub#eventhubon)订阅事件，跳转指定页面。
        
        aboutToAppear(): void {
          this.getUIContext().getHostContext()?.eventHub.on('navigation', (path: string) => {
            let paths = this.navPathStack.getAllPathName();
            if (paths.slice().pop() === path) {
              return;
            }
            this.navPathStack.pushPath({ name: path }, false);
          });
        }
        

*   **方案二**：封装NavPathStack，在onNewWant生命周期中通过getAllPathName获取栈中所有NavDestination页面的名称，判断当前页面是否为目标页面，再决定是否用NavPathStack将目标页面push至栈顶。
    1.  封装全局的NavPathStack变量。RouterModule.ets文件。
        
        export class RouterModule {
          private static pathStack: NavPathStack = new NavPathStack();
        
          static setRouterStack(pathStack: NavPathStack) {
            RouterModule.pathStack = pathStack;
          }
        
          static getRouterStatic() {
            return RouterModule.pathStack;
          }
        }
        
    2.  在被拉起方UIAbility的[onNewWant](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onnewwant)方法中（若UIAbility文件无此方法，则自行增加即可），执行判断和跳转操作。
        
        const pathStack = RouterModule.getRouterStatic();
        let param = want.parameters as Record<string, Object>;
        let backPage = param.message as string;
        if (pathStack.getAllPathName().slice().pop() === backPage) {
          return;
        }
        const pathInfo = new NavPathInfo(backPage, null);
        pathStack.pushPath(pathInfo);
        

完整示例如下：

新增工程项目后，此时存在一个entry类型的HAP包（作为拉起方），再新增一个feature类型的HAP（被拉起方）。参考不同类型[HAP](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hap-package)的说明。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/8d/v3/7ndlKcceTkC8OSr-vNdqcg/zh-cn_image_0000002628755346.png?HW-CC-KV=V1&HW-CC-Date=20260709T134101Z&HW-CC-Expire=86400&HW-CC-Sign=1AA5593D016C891792216EB71BD894708757FE157A009583451687AAD1C9BE6B)

1.  拉起方（entry模块）的示例代码如下，请根据实际的HAP信息更新want参数：
    
    import { common } from '@kit.AbilityKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    const BUNDLE\_NAME: string = 'com.example.NewWantDemo'; _// 在应用app.json5文件中'bundleName'节点获得_
    const ABILITY\_NAME: string = 'TargetAbility'; _// 在HAP包的对应Ability文件中获得_
    
    @Entry
    @Component
    struct HAPRouterDemo {
      private context?: common.UIAbilityContext; _// 创建context实例_
    
      aboutToAppear(): void {
        this.context = this.getUIContext().getHostContext() as common.UIAbilityContext; _// 获取当前页面关联的UIAbilityContext_
      }
    
      jumpHap() {
        if (this.context) {
          _// 启动Ability，拉起HAP模块的UIAbility实例_
          this.context.startAbility({
            bundleName: BUNDLE\_NAME,
            abilityName: ABILITY\_NAME,
            parameters: {
              'path': 'NavDesPage'
            }
          }).then(() => {
            console.info('start audio ability success');
          }).catch((error: BusinessError) => {
            console.error(\`start audio ability failed, error: ${error}\`);
          });
        }
      }
    
      build() {
        Column() {
          Button('startAbility跳转HAP')
            .onClick(() => {
              this.jumpHap();
            });
        }
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .width('100%');
      }
    }
    
2.  被拉起方（target模块）的示例代码如下：
    
    import { RouterModule } from './RouterModule';
    
    @Entry
    @Component
    struct Solution1 {
      navPathStack: NavPathStack = RouterModule.getRouterStatic();
    
      aboutToAppear(): void {
        this.getUIContext().getHostContext()?.eventHub.on('navigation', (path: string) => {
          let paths = this.navPathStack.getAllPathName();
          if (paths.slice().pop() === path) {
            return;
          }
          this.navPathStack.pushPath({ name: path }, false);
        });
      }
    
      @Builder
      pageMap(name: string) {
        if (name === 'NavDesPage') {
          NavDesPage();
        }
      }
    
      build() {
        Navigation(this.navPathStack) {
          Column() {
            Button('跳转NavDestination')
              .onClick(() => {
                this.navPathStack.pushPathByName('NavDesPage', null, false);
              });
          }.height('100%').width('100%').justifyContent(FlexAlign.Center);
        }.navDestination(this.pageMap)
        .height('100%').width('100%');
      }
    }
    
    @Component
    struct NavDesPage {
      navPathStack: NavPathStack = new NavPathStack();
    
      build() {
        NavDestination() {
          Text('NavDesPage')
            .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((ctx: NavDestinationContext) => {
          this.navPathStack = ctx.pathStack;
        })
        .height('100%')
        .width('100%');
      }
    }
    
3.  按照方案的描述修改被拉起方的UIAbility文件（示例中为TargetAbility.ets）的onNewWant方法（若UIAbility文件无此方法，则参照背景知识中介绍自行增加onNewWant即可）。
4.  分别编译target模块和entry模块，再运行entry模块即可。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/23/v3/OZI4r7oaTAykKC3RFz4Czw/zh-cn_image_0000002658954667.png?HW-CC-KV=V1&HW-CC-Date=20260709T134101Z&HW-CC-Expire=86400&HW-CC-Sign=308A18ECC5543169BCD8DC5C8333513829BC0135996253E16B6D0A229E128FC4)
    

#### 常见FAQ

Q：热启动，如何每次启动都重新打开目标页面？

A：可通过上述两种方案getAllPathName获取到栈中所有NavDestination页面的名称，判断目标页面是否在栈内，如在栈内则通过[removeByName](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-navigation#removebyname11)将目标页面从页面栈内删除，再通过pushPath入栈，达到重新打开的效果。示例代码如下：结合上述方案二，在UIAbility文件的[onNewWant](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onnewwant)方法中增加以下代码（若UIAbility文件无此方法，则自行增加即可）。

const pathStack = RouterModule.getRouterStatic();
let param = want.parameters as Record<string, Object>;
let targetPage = param.message as string;
const isPageBInStack = pathStack.getAllPathName().indexOf(targetPage) !== -1;
if (isPageBInStack) {
  pathStack.removeByName(targetPage);
  const pathInfo = new NavPathInfo(targetPage, null);
  pathStack.pushPath(pathInfo);
} else {
  const pathInfo = new NavPathInfo(targetPage, null);
  pathStack.pushPath(pathInfo);
}