鸿蒙中 跨HAP包页面的跳转

本文同步发表于微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、问题

   在多 HAP(HarmonyOS Ability Package)开发中,无法直接通过 Navigation 或 Router 实现跨 HAP 包的页面跳转,系统会抛出跳转失败。

原因

  • 路径隔离:每个 HAP 包内的页面路径对其他 HAP 不可见

  • 架构限制:Router/Navigation 设计用于单 HAP 内的页面导航或 HAP 到 HAR/HSP 的跳转

  • 模块独立性:每个 HAP 有独立的 UIAbility,运行时才加载对应资源

多 HAP 架构

┌─────────────────────────────────┐
│       多HAP应用架构              │
├─────────────────────────────────┤
│  Entry HAP(主入口)             │
│    └─ MainAbility               │
├─────────────────────────────────┤
│  Feature HAP 1(功能模块1)      │
│    └─ FeatureAbility1           │
├─────────────────────────────────┤
│  Feature HAP 2(功能模块2)      │
│    └─ FeatureAbility2           │
└─────────────────────────────────┘
  • 每个 HAP 可独立开发、测试和更新

  • 提高大型应用的开发效率和维护性

  • 支持功能模块的按需加载

二、跨 HAP包跳转方法

2.1 使用 startAbility 拉起目标 UIAbility

2.1.1 源 HAP 代码
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct SourcePage {
  @State message: string = '准备跳转';
  
  // 获取 UIAbility 上下文
  private abilityContext?: common.UIAbilityContext;
  
  aboutToAppear(): void {
    // 获取当前页面关联的 UIAbilityContext
    this.abilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  }
  
  // 跳转到目标 HAP
  private jumpToTargetHap() {
    if (!this.abilityContext) {
      console.error('无法获取 Ability 上下文');
      return;
    }
    
    const targetBundleName = 'com.example.shopping';  // 目标 HAP 的包名
    const targetAbilityName = 'ShoppingAbility';      // 目标 UIAbility 名称
    
    // 调用 startAbility 启动目标 HAP
    this.abilityContext.startAbility({
      bundleName: targetBundleName,
      abilityName: targetAbilityName,
      parameters: {
        // 可选的传递参数
        userId: 'user123',
        productId: 'p456',
        source: 'main_app'
      }
    }).then(() => {
      console.info('成功启动目标 HAP 的 Ability');
    }).catch((error: BusinessError) => {
      console.error(`启动失败,错误代码: ${error.code}, 详情: ${error.message}`);
    });
  }
  
  build() {
    Column({ space: 20 }) {
      Text(this.message)
        .fontSize(20)
        .fontColor(Color.Black)
      
      Button('跳转到购物模块')
        .width('80%')
        .height(50)
        .backgroundColor('#007DFF')
        .fontColor(Color.White)
        .fontSize(18)
        .onClick(() => {
          this.jumpToTargetHap();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}
2.1.2 获取 bundleName 和 abilityName 的方法
  1. bundleName:在目标 HAP 的 app.json5 文件的 bundleName 节点获取

  2. abilityName:在目标 HAP 的对应 Ability 文件或 module.json5 中获取

2.2 目标 HAP 配置

2.2.1 module.json5 配置
{
  "module": {
    "name": "shopping",
    "type": "feature",
    "abilities": [
      {
        "name": "ShoppingAbility",
        "srcEntry": "./ets/shoppingability/ShoppingAbility.ets",
        "description": "$string:shoppingability_desc",
        "icon": "$media:shopping_icon",
        "label": "$string:ShoppingAbility_label",
        "exported": true,  // 关键:必须设置为 true 允许外部调用
        "launchType": "singleton",
        "orientation": "unspecified",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "removeMissionAfterTerminate": true
      }
    ]
  }
}

关键说明

  • exported: true:允许其他应用/服务启动此 Ability

  • launchType:启动模式(singleton, standard, specified)

  • orientation:屏幕方向设置

2.3 目标 UIAbility 接收参数和页面跳转

import { UIAbility, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { Want } from '@kit.AbilityKit';

export default class ShoppingAbility extends UIAbility {
  // 存储接收到的参数
  private receivedParams: Record<string, any> = {};
  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    console.info('ShoppingAbility onCreate');
    
    // 保存启动参数
    this.receivedParams = want.parameters || {};
    console.info('接收到的参数:', this.receivedParams);
  }
  
  onWindowStageCreate(windowStage: window.WindowStage) {
    console.info('ShoppingAbility onWindowStageCreate');
    
    // 根据参数决定跳转到哪个页面
    let targetPage = 'pages/ShoppingHomePage';
    
    if (this.receivedParams.productId) {
      // 如果有商品ID,直接跳转到商品详情页
      targetPage = 'pages/ProductDetailPage';
    } else if (this.receivedParams.searchKeyword) {
      // 如果有搜索关键词,跳转到搜索结果页
      targetPage = 'pages/SearchResultPage';
    }
    
    // 加载页面,并传递参数
    windowStage.loadContent(targetPage, (err) => {
      if (err) {
        console.error('加载页面失败:', err);
        // 可以在这里处理加载失败的逻辑
        windowStage.loadContent('pages/ErrorPage', () => {});
      } else {
        console.info(`成功加载页面: ${targetPage}`);
      }
    });
  }
  
  onForeground() {
    console.info('ShoppingAbility onForeground');
  }
  
  onBackground() {
    console.info('ShoppingAbility onBackground');
  }
  
  onDestroy() {
    console.info('ShoppingAbility onDestroy');
  }
}

三、不同参数传递与处理

// 源 HAP 传递参数
this.abilityContext.startAbility({
  bundleName: 'com.example.shopping',
  abilityName: 'ShoppingAbility',
  parameters: {
    // 基本数据类型
    userId: 'user_001',
    isVip: true,
    level: 3,
    
    // 复杂数据(需可序列化)
    userInfo: {
      name: '张三',
      age: 28,
      address: '北京市'
    },
    
    // 数组数据
    productIds: ['p001', 'p002', 'p003'],
    
    // 时间戳
    timestamp: Date.now()
  }
});

参数接收与验证

// 在目标 Ability 中验证参数
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  const params = want.parameters || {};
  
  // 参数验证
  if (!params.userId) {
    console.warn('缺少必要参数: userId');
    // 可以设置默认值或记录日志
  }
  
  // 参数类型转换
  const pageType = String(params.pageType || 'default');
  const productId = params.productId ? String(params.productId) : null;
  
  // 存储验证后的参数
  this.validatedParams = {
    pageType,
    productId,
    userId: params.userId || 'anonymous',
    timestamp: Number(params.timestamp || Date.now())
  };
}

四、注意事项

4.1 配置要求

  • 目标 HAP 的 Ability 配置了 exported: true

  • bundleName 和 abilityName 准确无误

  • 目标 HAP 已正确安装且未被卸载

  • 参数数据类型支持序列化

  • 页面路径在目标 HAP 中存在

4.2 参数传递限制

数据类型是否支持注意事项
字符串、数字、布尔值推荐使用
对象、数组需可序列化为 JSON
函数、类实例无法传递
大数据对象⚠️可能影响性能,建议压缩

4.3 常见错误

this.abilityContext.startAbility({
  // 配置
}).catch((error: BusinessError) => {
  switch (error.code) {
    case 201:
      console.error('启动失败:目标 Ability 未导出');
      // 检查目标 module.json5 中的 exported 配置
      break;
      
    case 202:
      console.error('启动失败:目标 Ability 不存在');
      // 检查 bundleName 和 abilityName 拼写
      break;
      
    case 1600001:
      console.error('启动失败:参数不合法');
      // 检查 parameters 格式
      break;
      
    default:
      console.error(`未知错误: ${error.code}, ${error.message}`);
      break;
  }
});

五、与 HAR/HSP 跳转对比

特性维度HAP 间跳转HAR/HSP 跳转
跳转方式startAbility 启动 UIAbilityrouter.pushUrl / Navigation
代码耦合度低(仅依赖 Ability 名称)中(依赖路由路径/名称)
运行时性能较高(需启动新 Ability)高(直接加载页面)
参数传递通过 parameters 参数传递通过路由参数传递
模块独立性完全独立,按需加载编译时依赖,打包时集成
更新灵活性可独立更新 HAP 模块需重新编译发布
适用场景独立功能模块、按需加载高频交互的共享模块
选择 HAP 间跳转的场景:
  • 功能模块完全独立,很少与其他模块交互

  • 需要按需加载,减少应用初始包大小

  • 模块可能独立更新,无需重新发布整个应用

  • 业务逻辑隔离,模块间数据交换较少

选择 HAR/HSP 的场景:
  • 功能紧密耦合,频繁跨模块调用

  • 共享 UI 组件,需要统一的界面风格

  • 高频交互,要求快速响应

  • 代码复用,多个 HAP 使用相同功能

六、开发建议

  1. 尽量减少 HAP 数量:避免过度拆分带来的通信复杂性

  2. 合理划分模块边界:确保每个 HAP 有清晰的职责

  3. 统一通信协议:定义标准的参数格式和错误处理

创建跨 HAP 通信的统一工具类

class HapNavigationManager {
  private static instance: HapNavigationManager;
  
  static getInstance(): HapNavigationManager {
    if (!this.instance) {
      this.instance = new HapNavigationManager();
    }
    return this.instance;
  }
  
  // 跳转到指定 HAP
  async navigateToHap(
    bundleName: string, 
    abilityName: string, 
    params?: Record<string, any>
  ): Promise<boolean> {
    // 统一错误处理
    // 参数验证和转换
    // 日志记录
    // 性能监控
  }
  
  // 检查 HAP 是否可用
  async isHapAvailable(bundleName: string): Promise<boolean> {
    // 实现检查逻辑
  }
}

如果有频繁的 HAP 间跳转,可以考虑重构为 HSP:

// 重构前:HAP 间跳转(性能开销大)
this.context.startAbility({
  bundleName: 'com.example.payment',
  abilityName: 'PaymentAbility'
});

// 重构后:HSP 内跳转(性能更优)
router.pushUrl({
  url: '@bundle:com.example.common/hsp/payment/PaymentPage',
  params: {
    amount: 100.00,
    orderId: 'order_123'
  }
});

总结

   跨 HAP 包页面跳转通过 startAbility 机制可以实现模块间的解耦和按需加载,但需要注意配置正确性、参数传递限制和性能影响。

核心要点

  1. 必须配置 exported: true 允许外部启动

  2. 准确传递 bundleName 和 abilityName

  3. 合理设计参数传递,避免大数据量

  4. 考虑替代方案:频繁跳转的场景建议使用 HAR/HSP

  5. 完善错误处理:处理各种启动失败情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值