本文同步发表于微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、问题
在多 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 的方法
-
bundleName:在目标 HAP 的
app.json5文件的bundleName节点获取 -
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 启动 UIAbility | router.pushUrl / Navigation |
| 代码耦合度 | 低(仅依赖 Ability 名称) | 中(依赖路由路径/名称) |
| 运行时性能 | 较高(需启动新 Ability) | 高(直接加载页面) |
| 参数传递 | 通过 parameters 参数传递 | 通过路由参数传递 |
| 模块独立性 | 完全独立,按需加载 | 编译时依赖,打包时集成 |
| 更新灵活性 | 可独立更新 HAP 模块 | 需重新编译发布 |
| 适用场景 | 独立功能模块、按需加载 | 高频交互的共享模块 |
选择 HAP 间跳转的场景:
-
功能模块完全独立,很少与其他模块交互
-
需要按需加载,减少应用初始包大小
-
模块可能独立更新,无需重新发布整个应用
-
业务逻辑隔离,模块间数据交换较少
选择 HAR/HSP 的场景:
-
功能紧密耦合,频繁跨模块调用
-
共享 UI 组件,需要统一的界面风格
-
高频交互,要求快速响应
-
代码复用,多个 HAP 使用相同功能
六、开发建议
-
尽量减少 HAP 数量:避免过度拆分带来的通信复杂性
-
合理划分模块边界:确保每个 HAP 有清晰的职责
-
统一通信协议:定义标准的参数格式和错误处理
创建跨 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 机制可以实现模块间的解耦和按需加载,但需要注意配置正确性、参数传递限制和性能影响。
核心要点:
-
必须配置
exported: true允许外部启动 -
准确传递 bundleName 和 abilityName
-
合理设计参数传递,避免大数据量
-
考虑替代方案:频繁跳转的场景建议使用 HAR/HSP
-
完善错误处理:处理各种启动失败情况
1084

被折叠的 条评论
为什么被折叠?



