HarmonyOS APP开发中的应用信息大揭秘
HarmonyOS APP开发中的应用信息:@ohos.bundleManager 与应用包信息全解析
📌 核心要点:掌握 @ohos.bundleManager 获取应用包信息的完整方案,深入理解 BundleInfo/ModuleInfo/HapModuleInfo 的层级关系,实现应用签名校验与版本管理。
一、背景与动机
想象一下这个场景:你开发了一款社交应用,用户在「关于」页面想看看当前版本号;你的应用需要检查另一个应用是否已安装才能跳转;你的后台服务需要验证应用签名来确保通信安全……这些需求看似五花八门,但归根结底,都指向同一件事——获取应用信息。
在 HarmonyOS 中,应用信息并不是一个简单的字符串或数字,它是一棵结构化的信息树。从最顶层的 BundleInfo,到中间的 ModuleInfo,再到叶子节点的 HapModuleInfo,每一层都承载着不同粒度的信息。理解这棵树的结构,就像拿到了应用世界的「户口本」——你不仅能查到自己的信息,还能查到别人的。
更关键的是,应用签名信息是安全体系的基石,版本管理是 OTA 更新的前提。如果你对这些概念还停留在「知道个大概」的阶段,那这篇文章就是为你准备的。
二、核心原理
2.1 应用包信息层级结构
HarmonyOS 的应用包信息采用三层结构,就像俄罗斯套娃一样层层嵌套:
graph TD
A[BundleInfo 应用包信息] --> B[ModuleInfo 模块信息]
B --> C[HapModuleInfo HAP模块详情]
A --> D[AppInfo 应用基础信息]
A --> E[SignatureInfo 签名信息]
A --> F[VersionInfo 版本信息]
C --> G[AbilityInfo Ability信息]
C --> H[ExtensionAbilityInfo 扩展能力信息]
C --> I[Metadata 元数据]
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
class A,B,C primary
class D,E,F info
class G,H,I purple
层级解读:
| 层级 | 类名 | 含义 | 类比 |
|---|---|---|---|
| 第一层 | BundleInfo | 整个应用包的信息 | 一本书的整体信息 |
| 第二层 | ModuleInfo | 应用中某个模块的信息 | 书中某个章节的信息 |
| 第三层 | HapModuleInfo | HAP 模块的详细信息 | 章节中每页的详细内容 |
2.2 bundleManager 核心方法
flowchart LR
subgraph 同步方法
A1[getBundleInfoForSelf] --> A2[获取自身应用信息]
end
subgraph 异步方法
B1[getBundleInfo] --> B2[获取指定应用信息]
B3[getAllBundleInfo] --> B4[获取所有应用信息]
B5[getApplicationInfo] --> B6[获取应用基础信息]
end
subgraph 查询方法
C1[queryAbilityInfo] --> C2[查询Ability信息]
C3[queryExtensionAbilityInfo] --> C4[查询扩展能力信息]
end
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
class A1,A2 primary
class B1,B2,B3,B4,B5,B6 warning
class C1,C2,C3,C4 info
关键区别:
getBundleInfoForSelf:同步方法,无需权限,获取自身信息getBundleInfo:异步方法,需要权限,获取其他应用信息getAllBundleInfo:系统应用专用,获取所有已安装应用
2.3 版本号体系
HarmonyOS 采用双版本号体系:
- versionCode(整数):用于程序比较,如
1000000,每次更新必须递增 - versionName(字符串):用于用户展示,如
"1.0.0",遵循语义化版本规范
版本比较规则:先比 versionCode,再比 versionName。应用市场判断是否需要更新,核心依据就是 versionCode 是否递增。
三、代码实战
3.1 获取自身应用完整信息
这是最基础也是最常用的场景——应用获取自身信息,无需任何权限。
import { bundleManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct AppInfoPage {
// 应用信息状态
@State bundleName: string = '';
@State versionCode: number = 0;
@State versionName: string = '';
@State appName: string = '';
@State vendor: string = '';
@State minAPIVersion: number = 0;
@State targetAPIVersion: number = 0;
@State moduleCount: number = 0;
@State hapModuleNames: string[] = [];
aboutToAppear() {
this.getSelfBundleInfo();
}
/**
* 获取自身应用包信息
* 使用同步方法 getBundleInfoForSelf,无需权限
*/
getSelfBundleInfo() {
try {
// 获取自身应用信息,传入查询标志位
// BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE:包含HAP模块信息
// BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY:包含Ability信息
// BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION:包含应用基础信息
const bundleInfo: bundleManager.BundleInfo =
bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
);
// 提取基础应用信息
this.bundleName = bundleInfo.name;
this.versionCode = bundleInfo.versionCode;
this.versionName = bundleInfo.versionName;
this.vendor = bundleInfo.vendor;
this.minAPIVersion = bundleInfo.minCompatibleVersionCode;
this.targetAPIVersion = bundleInfo.targetVersionCode;
// 提取应用名称
if (bundleInfo.appInfo) {
this.appName = bundleInfo.appInfo.label;
}
// 提取模块信息
this.moduleCount = bundleInfo.hapModulesInfo.length;
this.hapModuleNames = bundleInfo.hapModulesInfo.map(module => {
return module.name;
});
console.info(`[AppInfo] 应用包名: ${this.bundleName}`);
console.info(`[AppInfo] 版本: ${this.versionName}(${this.versionCode})`);
console.info(`[AppInfo] 模块数: ${this.moduleCount}`);
} catch (err) {
const error = err as BusinessError;
console.error(`[AppInfo] 获取应用信息失败: ${error.code} - ${error.message}`);
}
}
build() {
Column() {
// 标题栏
Text('应用信息')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 信息展示列表
List({ space: 12 }) {
this.InfoItem('应用名称', this.appName)
this.InfoItem('包名', this.bundleName)
this.InfoItem('版本号', `${this.versionName} (${this.versionCode})`)
this.InfoItem('开发商', this.vendor)
this.InfoItem('最低API版本', `${this.minAPIVersion}`)
this.InfoItem('目标API版本', `${this.targetAPIVersion}`)
this.InfoItem('模块数量', `${this.moduleCount}`)
this.InfoItem('模块列表', this.hapModuleNames.join(', '))
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
}
@Builder
InfoItem(label: string, value: string) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#999999')
.width(120)
Text(value || '未知')
.fontSize(14)
.fontColor('#333333')
.layoutWeight(1)
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
}
3.2 获取指定应用的签名信息
签名校验是安全通信的基础,比如你想确认与你通信的应用确实是合法的。
import { bundleManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
@Entry
@Component
struct SignatureInfoPage {
@State signatureFingerprint: string = '正在获取...';
@State appId: string = '';
@State appIdentifier: string = '';
@State isVerified: boolean = false;
// 目标应用的包名(示例)
private targetBundleName: string = 'com.example.targetapp';
aboutToAppear() {
this.getTargetAppSignature();
}
/**
* 获取指定应用的签名信息
* 需要权限:ohos.permission.GET_BUNDLE_INFO_PRIVILEGED(系统应用)
* 或 ohos.permission.GET_BUNDLE_INFO(普通应用,仅限自身)
*/
async getTargetAppSignature() {
try {
// 使用异步方法获取指定应用的签名信息
const bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfo(
this.targetBundleName,
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO
);
// 获取签名信息
if (bundleInfo.appInfo && bundleInfo.appInfo.signatureInfo) {
const signInfo = bundleInfo.appInfo.signatureInfo;
// 应用标识(HarmonyOS 专属,跨设备唯一)
this.appIdentifier = signInfo.appIdentifier || '无';
// 应用ID
this.appId = bundleInfo.appInfo.appId || '无';
// 指纹信息(用于签名校验)
this.signatureFingerprint = signInfo.fingerprint || '无';
// 执行签名校验
this.verifySignature(signInfo.fingerprint);
}
console.info(`[Signature] appId: ${this.appId}`);
console.info(`[Signature] appIdentifier: ${this.appIdentifier}`);
} catch (err) {
const error = err as BusinessError;
console.error(`[Signature] 获取签名信息失败: ${error.code} - ${error.message}`);
this.signatureFingerprint = '获取失败';
}
}
/**
* 签名校验逻辑
* 将获取到的指纹与预置的合法指纹进行比对
*/
verifySignature(fingerprint: string) {
// 预置的合法指纹(实际项目中应从安全配置中读取)
const TRUSTED_FINGERPRINT = 'your_trusted_fingerprint_here';
if (fingerprint === TRUSTED_FINGERPRINT) {
this.isVerified = true;
console.info('[Signature] 签名校验通过');
} else {
this.isVerified = false;
console.warn('[Signature] 签名校验失败,指纹不匹配');
}
}
/**
* 获取自身应用的签名信息(无需权限)
*/
getSelfSignature() {
try {
const bundleInfo = bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO
);
if (bundleInfo.appInfo?.signatureInfo) {
const signInfo = bundleInfo.appInfo.signatureInfo;
console.info(`[SelfSignature] appId: ${bundleInfo.appInfo.appId}`);
console.info(`[SelfSignature] appIdentifier: ${signInfo.appIdentifier}`);
console.info(`[SelfSignature] fingerprint: ${signInfo.fingerprint}`);
return signInfo;
}
} catch (err) {
const error = err as BusinessError;
console.error(`[SelfSignature] 获取自身签名失败: ${error.code} - ${error.message}`);
}
return null;
}
build() {
Column() {
Text('应用签名信息')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 签名校验结果
Row() {
Text(this.isVerified ? '✓ 签名校验通过' : '✗ 签名校验失败')
.fontSize(16)
.fontColor(this.isVerified ? '#4CAF50' : '#F44336')
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
.backgroundColor(this.isVerified ? '#E8F5E9' : '#FFEBEE')
.borderRadius(8)
.margin({ bottom: 16 })
// 详细信息
Column({ space: 12 }) {
this.SignatureItem('目标包名', this.targetBundleName)
this.SignatureItem('App ID', this.appId)
this.SignatureItem('App Identifier', this.appIdentifier)
this.SignatureItem('签名指纹', this.signatureFingerprint)
}
}
.width('100%')
.height('100%')
.padding(16)
}
@Builder
SignatureItem(label: string, value: string) {
Column() {
Text(label)
.fontSize(12)
.fontColor('#999999')
.margin({ bottom: 4 })
Text(value || '未知')
.fontSize(14)
.fontColor('#333333')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
}
3.3 版本管理与比较工具
版本比较是应用更新的前置步骤,这里封装一个完整的版本管理工具类。
import { bundleManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 版本管理工具类
* 封装版本号比较、解析、格式化等常用操作
*/
export class VersionManager {
private static instance: VersionManager;
private currentVersionCode: number = 0;
private currentVersionName: string = '';
private constructor() {
this.loadCurrentVersion();
}
/**
* 单例获取
*/
static getInstance(): VersionManager {
if (!VersionManager.instance) {
VersionManager.instance = new VersionManager();
}
return VersionManager.instance;
}
/**
* 加载当前应用版本信息
*/
private loadCurrentVersion() {
try {
const bundleInfo = bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
);
this.currentVersionCode = bundleInfo.versionCode;
this.currentVersionName = bundleInfo.versionName;
} catch (err) {
const error = err as BusinessError;
console.error(`[VersionManager] 加载版本信息失败: ${error.message}`);
}
}
/**
* 获取当前版本号(数字)
*/
getCurrentVersionCode(): number {
return this.currentVersionCode;
}
/**
* 获取当前版本名(字符串)
*/
getCurrentVersionName(): string {
return this.currentVersionName;
}
/**
* 比较版本号
* @returns 1 表示 current > target,0 表示相等,-1 表示 current < target
*/
compareVersion(currentCode: number, targetCode: number): number {
if (currentCode > targetCode) return 1;
if (currentCode < targetCode) return -1;
return 0;
}
/**
* 解析语义化版本号
* 将 "1.2.3" 解析为 { major: 1, minor: 2, patch: 3 }
*/
parseSemanticVersion(versionName: string): { major: number; minor: number; patch: number } {
const parts = versionName.split('.');
return {
major: parseInt(parts[0]) || 0,
minor: parseInt(parts[1]) || 0,
patch: parseInt(parts[2]) || 0
};
}
/**
* 比较语义化版本名
* 逐段比较 major → minor → patch
*/
compareSemanticVersion(v1: string, v2: string): number {
const ver1 = this.parseSemanticVersion(v1);
const ver2 = this.parseSemanticVersion(v2);
if (ver1.major !== ver2.major) {
return ver1.major > ver2.major ? 1 : -1;
}
if (ver1.minor !== ver2.minor) {
return ver1.minor > ver2.minor ? 1 : -1;
}
if (ver1.patch !== ver2.patch) {
return ver1.patch > ver2.patch ? 1 : -1;
}
return 0;
}
/**
* 判断是否需要更新
* @param remoteVersionCode 远端版本号
* @param remoteVersionName 远端版本名
* @returns 是否需要更新
*/
needUpdate(remoteVersionCode: number, remoteVersionName: string): boolean {
// 优先比较 versionCode(数字比较更可靠)
const codeCompare = this.compareVersion(remoteVersionCode, this.currentVersionCode);
if (codeCompare > 0) return true;
if (codeCompare < 0) return false;
// versionCode 相同时,再比较 versionName
const nameCompare = this.compareSemanticVersion(remoteVersionName, this.currentVersionName);
return nameCompare > 0;
}
/**
* 格式化版本信息
*/
formatVersionInfo(): string {
return `v${this.currentVersionName} (build ${this.currentVersionCode})`;
}
}
// ============ 在页面中使用 ============
@Entry
@Component
struct VersionManagerPage {
@State currentVersion: string = '';
@State remoteVersion: string = '2.1.0';
@State remoteVersionCode: number = 2001000;
@State needUpdate: boolean = false;
@State compareResult: string = '';
private versionManager: VersionManager = VersionManager.getInstance();
aboutToAppear() {
this.currentVersion = this.versionManager.formatVersionInfo();
this.checkUpdate();
}
/**
* 检查更新逻辑
*/
checkUpdate() {
this.needUpdate = this.versionManager.needUpdate(
this.remoteVersionCode,
this.remoteVersion
);
const compareResult = this.versionManager.compareSemanticVersion(
this.versionManager.getCurrentVersionName(),
this.remoteVersion
);
if (compareResult < 0) {
this.compareResult = `当前版本低于远端版本,建议更新`;
} else if (compareResult > 0) {
this.compareResult = `当前版本高于远端版本,可能是开发版`;
} else {
this.compareResult = `已是最新版本`;
}
}
build() {
Column() {
Text('版本管理')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 })
// 当前版本信息
Column() {
Text('当前版本')
.fontSize(14)
.fontColor('#999999')
.margin({ bottom: 8 })
Text(this.currentVersion)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ bottom: 16 })
// 远端版本信息
Column() {
Text('远端版本')
.fontSize(14)
.fontColor('#999999')
.margin({ bottom: 8 })
Text(`v${this.remoteVersion} (build ${this.remoteVersionCode})`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ bottom: 16 })
// 比较结果
Column() {
Text(this.compareResult)
.fontSize(16)
.fontColor(this.needUpdate ? '#FF9800' : '#4CAF50')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding(16)
.backgroundColor(this.needUpdate ? '#FFF3E0' : '#E8F5E9')
.borderRadius(12)
.margin({ bottom: 16 })
// 更新按钮
if (this.needUpdate) {
Button('立即更新')
.width('100%')
.height(48)
.backgroundColor('#4CAF50')
.fontColor('#FFFFFF')
.fontSize(16)
.borderRadius(24)
.onClick(() => {
console.info('[VersionManager] 用户点击更新');
})
}
}
.width('100%')
.height('100%')
.padding(16)
}
}
四、踩坑与注意事项
4.1 权限陷阱
| 场景 | 所需权限 | 说明 |
|---|---|---|
| 获取自身信息 | 无需权限 | getBundleInfoForSelf 是同步方法,零门槛 |
| 获取其他应用信息 | ohos.permission.GET_BUNDLE_INFO |
普通权限,用户授权即可 |
| 获取其他应用签名 | ohos.permission.GET_BUNDLE_INFO_PRIVILEGED |
系统权限,仅系统应用可用 |
踩坑:很多开发者用 getBundleInfo 去获取自身信息,其实完全没必要。getBundleInfoForSelf 是同步方法,性能更好,还不需要权限。
4.2 BundleFlag 组合使用
BundleFlag 是位掩码,可以用 | 组合。但组合越多,返回数据越大,耗时越长。
// ❌ 不推荐:无脑全选
const flags = 0xFFFFFFFF;
// ✅ 推荐:按需选择
const flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
4.3 HapModuleInfo 的 mainAbility 陷阱
在 HarmonyOS 5.0+ 中,mainAbility 字段已被废弃,取而代之的是 mainElementName。如果你的代码还在读取 mainAbility,请尽快迁移。
4.4 版本号的 versionCode 陷阱
versionCode 是整数,但不是简单的 1, 2, 3。推荐使用以下编码规则:
versionCode = major * 1000000 + minor * 1000 + patch
// 例如 1.2.3 → 1002003
// 例如 2.0.0 → 2000000
这样编码的好处是:数字比较和语义化比较的结果一致,不会出现 1.10 > 1.9 但 110 < 19 的尴尬。
4.5 异步方法的线程安全
getBundleInfo 等异步方法返回的是 Promise,在 async/await 中使用时要注意异常捕获。不要在 aboutToAppear 中使用 await 而不包裹 try/catch,否则未捕获的异常可能导致应用崩溃。
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 签名信息获取 | appInfo.signatureInfo |
新增 appInfo.certificateInfo 字段 |
| 模块信息 | hapModulesInfo |
新增 sharedModulesInfo(共享库模块) |
| 版本信息 | versionCode/versionName |
新增 compatibleVersion 字段 |
| 查询标志 | BundleFlag 枚举 |
新增 GET_BUNDLE_INFO_WITH_METADATA |
5.2 迁移指南
// HarmonyOS 5 写法
const bundleInfo = bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
);
// HarmonyOS 6 推荐写法(新增元数据查询)
const bundleInfo = bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA // 新增
);
5.3 新增能力
HarmonyOS 6 在 bundleManager 中新增了以下能力:
- 共享库信息查询:可获取应用依赖的共享库信息
- 应用沙箱路径:新增沙箱路径查询接口
- 多用户支持:支持查询指定用户下的应用信息
六、总结
mindmap
root((应用信息))
核心API
getBundleInfoForSelf
同步方法
无需权限
获取自身信息
getBundleInfo
异步方法
需要权限
获取指定应用信息
信息层级
BundleInfo
应用包完整信息
ModuleInfo
模块基础信息
HapModuleInfo
HAP模块详情
签名信息
appId
appIdentifier
fingerprint
版本管理
versionCode
整数比较
versionName
语义化版本
版本比较工具
注意事项
权限选择
BundleFlag按需组合
versionCode编码规范
核心知识点回顾:
- 三层信息结构:BundleInfo → ModuleInfo → HapModuleInfo,从整体到局部层层递进
- 两个核心方法:
getBundleInfoForSelf(同步、无权限)和getBundleInfo(异步、需权限) - 签名三要素:appId(应用ID)、appIdentifier(跨设备标识)、fingerprint(签名指纹)
- 版本双轨制:versionCode 用于程序比较,versionName 用于用户展示
- BundleFlag 按需组合:不要无脑全选,按需组合才能兼顾性能和功能
- HarmonyOS 6 新增:元数据查询、共享库信息、多用户支持
掌握应用信息的获取和管理,是构建安全、可靠应用的基础。从版本比较到签名校验,从模块查询到信息展示,每一个环节都值得深入理解。
- 点赞
- 收藏
- 关注作者
评论(0)