鸿蒙百篇系列第 68 篇 · 中级
第 20 篇讲上架流程是从"运维"角度讲的。这一篇我们钻进底层"包管理子系统",把 HAP / HSP / APP 的实质、签名链、版本号、安装卸载原理以及 bm 工具搞清楚。理解这一层之后,遇到"包安装失败"、“版本冲突”、"动态下发模块"这类问题就有数了。
一、HAP 是鸿蒙应用的最小装载单位
一个鸿蒙应用在不同阶段看到的是不同的"装载体":
它们的物理结构(简化):
com.example.app.app # 顶层目录
├── pack.info # 整体信息
├── signature.p7s # 整体签名
├── entry.hap # 主模块
├── feature.hap # 业务特性包
└── shared_library.hsp # 共享包
每个 HAP 里又有:
hap/
├── pack.info # HAP 名称、version
├── resources # 资源索引
├── module.json5 # 模块配置
├── code.abc / code.deflate # 字节码
└── libs/ # 第三方 .so
HSP 的出现是为了打破"每个 HAP 重复装一份相同代码"的问题。多个 HAP 共享同一份 HSP,安装体积能减半,启动也更快。
二、HAP 的两种 mode
module.json5 里 deliveryWithInstall 字段决定 HAP 是否随主包安装:
按需下载的 HAP 在 requestPermissions 之外,会再注册一个 com.example.app.${feature} 的 feature 标识,用于按需 lazy load。
应用启动时按需动态加载 Feature HAP:
import { hilog } from '@kit.PerformanceAnalysisKit';
const moduleName = 'feature_news';
aboutToAppear() {
const module = require(moduleName);
this.newsPage = module.default;
}
// 或者用 initializeAbility / loadModule API
按需下载是大型应用瘦身最直接的手段。微信、抖音这种应用都是 entry + 多个 feature module 组合。
三、bundle 包结构体:BundleInfo 与 ApplicationInfo
运行时通过 bundleManager 模块查询包信息:
import bundleManager from '@ohos.bundle.bundleManager';
const bundleInfo = await bundleManager.getBundleInfo({
bundleName: 'com.example.app',
bundleFlag: bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SKILL |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO
});
console.info(`versionName: ${bundleInfo.versionName}`);
console.info(`versionCode: ${bundleInfo.versionCode}`);
console.info(`签名: ${bundleInfo.signatureInfo?.appId}`);
console.info(`HAP modules:`);
for (const m of bundleInfo.hapModulesInfo) {
console.info(` - ${m.name} (${m.hapPath})`);
}
关键字段:
versionCode 是鸿蒙做"版本比对"的依据。升级时必须大于旧版本,否则安装直接被拒。
四、安装和卸载:bm 命令与 API
bm 是鸿蒙提供的 CLI 工具,可以脱离 IDE 直接装包:
# 进入设备 shell
hdc shell
# 安装 HAP
bm install -p /data/app/entry.hap
# 覆盖安装
bm install -p /data/app/entry.hap -r
# 安装 HSP 共享包
bm install -s /data/app/shared.hsp
# 安装多包(一个 entry + 多个 feature)
bm install -p /data/app/entry.hap -s /data/app/lib1.hsp /data/app/lib2.hsp
# 等到一定时间再装(给 UI 闭环比如登录)
bm install -p /data/app/entry.hap -w 10
# 卸载
bm uninstall -n com.example.app
# 卸载但保留数据
bm uninstall -n com.example.app -k
# 卸载 HSP
bm uninstall -n com.example.shared -s
# 查询
bm dump -a # 全部包
bm dump -n com.example.app # 指定包
bm dump -s shortcutInfo -n com.example.app # 快捷方式
bm clean -n com.example.app # 清理缓存
注意 -p 装包路径应用 sandbox 不可写,要先用 hdc file recv 把文件传到设备可访问目录。
五、应用层面安装:startAbility
如果你想在自己的应用里启动"安装"流程,调起系统安装器:
import common from '@ohos.app.ability.common';
import { BusinessError } from '@kit.BasicServicesKit';
async function installBundle(context: common.UIAbilityContext, hapPath: string) {
// ohos.want.action.INSTALL_BUNDLE
const want = {
action: 'ohos.want.action.INSTALL_BUNDLE',
parameters: {
'ohos.extra.param.key.bundleFilePath': hapPath
}
};
try {
await context.startAbility(want);
} catch (e) {
const err = e as BusinessError;
console.error(`启动安装器失败: code=${err.code}, msg=${err.message}`);
}
}
需要 ohos.permission.INSTALL_BUNDLE 权限。这个 API 适合"在线下载更新包后即时安装"的场景,例如 OTA。
六、签名校验:HAP 的防篡改机制
每个 HAP 都有签名 signature.p7s:
由发布者私钥签名
内容是 HAP 文件 hash
AGC 上的发布证书链可校验
import bundleManager from '@ohos.bundle.bundleManager';
async function verifySignature(bundleName: string, expectedAppId: string) {
const info = await bundleManager.getBundleInfo({
bundleName,
bundleFlag: bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO
});
if (info.signatureInfo?.appId !== expectedAppId) {
return false;
}
return true;
}
签名应用面很广:
应用市场校验:每个上架包都校验 appId 与开发者实名证书是否一致
运行时校验:API 12 提供"动态加载模块必须经过签名校验",第三方插件要传入正确的 bundleInfo
跨设备调用:分布式调用通过签名算 appId 决定权限
调试签名 / 发布签名分离:开发期用调试证书签名方便上架,但发布版必须用 AGC 证书
七、版本号与升级
versionCode 是 int32 数字,定义在 AppScope 里:
{
"app": {
"bundleName": "com.example.app",
"versionCode": 1000210,
"versionName": "1.0.2-rc10"
}
}
升级规则:
设计 version 时的常用做法:
主版本号:兼容到 旧 3 个大版本
版本号:1000
次版本号:100
补丁版本号:1
最终: 1.2.10 → versionCode: 102110
八、应用类型与安装路径
app.bundleType 控制应用能否"原子化":
元服务的安装路径特殊,卸载时仅清理 user 级沙箱数据,共享代码可在另一处挂载。这一层是元服务"轻量"的基础。
九、几个工程坑
HSP 升级冲突。HSP 是共享包,多个 HAP 引用同一份 HSP。如果某个 HAP 升级引入新版本 HSP,老版本同时拉起会失败。设计时 HSP 版本要和 HAP 一起升级
按需下载失败兜底。网络差时按需下载失败,进入应用要提供"重试"路径
appId 与 bundleName 不同。每个应用有唯一 bundleName,签名后产生唯一 appId,前者是声明性,后者是计算性。混淆这两个导致权限链失败
bundle install -w时长:5-600 秒范围,默认 5 秒。下载更新包场景推荐 30+自动更新后状态恢复。升级后
want.parameters里的 deepLink 可能失效,要做路由兼容签名工具链鸿蒙与 Android 不通用。OpenHarmony 签名工具跟 APK 签名不同,老工具失效。开发者从 Android 过来第一道坎
十、查询能力:ApplicationInfo / HapModuleInfo / AbilityInfo
const data = await bundleManager.getBundleInfo({
bundleName: 'com.example.app',
bundleFlag: bundleManager.BundleFlag.GET_BUNDLE_INFO_ALL
});
// 全量信息
const app = data.appInfo; // ApplicationInfo
for (const m of data.hapModulesInfo) {
const module = m; // HapModuleInfo
for (const a of m.abilityInfos) {
const ability = a; // AbilityInfo
}
}
实战中几个用法:
// 1. 找到所有合法的 launchType='MAIN' 的 Ability
const mainAbility = data.hapModulesInfo
.flatMap(m => m.abilityInfos ?? [])
.find(a => a.launchType === bundleManager.LaunchType.SINGLETON);
// 2. 判断应用是否被卸载
const flags = data.appInfo.flags;
const isUninstalled = (flags & 0x1000) !== 0;
// 3. 给出所有 HAP 的 codePath
const codePaths = data.hapModulesInfo.map(m => m.codePath ?? m.hapPath);
十一、和前几篇的衔接
第 20 篇讲上架流程,本篇是底层细节
第 35 篇动态化,提到的卡片数据下发其实是基于 HAP/HSP 安装
第 36 篇小程序整合,跨平台资源调度也用 bm 命令
十二、小结
包管理是鸿蒙"操作系统级功能"里做得很稳的一块。理解 HAP 物理结构、HSP 共享、按需下载、bm CLI 工具、bundleManager 查询,能让你在大型应用工程里少踩很多坑。下次看到 bm install -r 报错版本号不递增,能立刻知道问题在哪。
下一篇进入系统服务框架(SAMgr),看 SA 怎么注册、怎么被发现、跨设备怎么调用。