动态下发:HSP动态特性的加载与更新机制(49)
在鸿蒙(HarmonyOS)生态中,关于“动态下发与更新”的机制,需要明确区分两个容易混淆的概念:HSP(动态共享包)与Feature HAP(动态特性模块)。虽然它们都带有“动态”属性,但在加载与更新机制上存在本质区别。
一、 核心概念澄清:HSP vs Feature HAP
1、 HSP(动态共享包):代码与资源的运行时复用
HSP 的核心作用是“抽离公共代码,减小包体积”。它不支持独立发布,而是随宿主应用一起打包,在运行时被多个模块动态加载。
1. 导出组件与工具类
在 HSP 模块(如 library)中,通过 export 将需要共享的组件、类或方法暴露出去,并在入口文件 index.ets 中统一声明:
// library/src/main/ets/utils/Logger.ets
export class Logger {
static info(msg: string): void {
console.info(`[HSP Shared Log]: ${msg}`);
}
}
// library/src/main/ets/components/SharedTitle.ets
@Component
export struct SharedTitle {
@Prop title: string;
build() {
Text(this.title).fontSize(24).fontWeight(FontWeight.Bold);
}
}
// library/index.ets (模块入口文件)
export { Logger } from './src/main/ets/utils/Logger';
export { SharedTitle } from './src/main/ets/components/SharedTitle';
2. 在 HAP 中动态调用
在主模块(Entry HAP)中,无需拷贝代码,直接引用 HSP 导出的接口即可。系统在运行时会自动加载该 HSP:
// entry/src/main/ets/pages/Index.ets
import { Logger, SharedTitle } from 'library'; // 引用 HSP 模块
@Entry
@Component
struct Index {
aboutToAppear() {
Logger.info('应用启动,HSP 模块已动态加载');
}
build() {
Column() {
SharedTitle({ title: 'HSP 动态共享示例' })
}
}
}
2、 Feature HAP(动态特性模块):按需下载与热更新
Feature HAP 是鸿蒙实现“动态下发”的核心。通过将非核心功能(如直播、AR 试穿)拆分为 Feature HAP,用户首次安装时不下载,在触发特定业务时再通过系统接口动态拉取。
1. 配置为按需下载模块
在 Feature HAP 的 module.json5 中,将 deliveryWithInstall 设置为 false,表示该模块不随主包安装:
// feature_live/module.json5
{
"module": {
"name": "feature_live",
"type": "feature",
"deliveryWithInstall": false // 核心配置:不随主应用安装,按需下载
}
}
2. 主模块配置动态依赖
在主模块的 oh-package.json5 中,使用 dynamicDependencies 声明对该 Feature 模块的动态依赖:
// entry/oh-package.json5
{
"dynamicDependencies": {
"feature_live": "file:../feature_live"
}
}
3. 运行时动态检查与下载
在用户点击相关功能时,通过 moduleInstallManager 检查模块是否已安装,若未安装则触发云端下载:
// entry/src/main/ets/pages/Index.ets
import { moduleInstallManager } from '@kit.AppGalleryKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct Index {
private async enterLiveRoom() {
const moduleName = 'feature_live';
// 1. 检查模块是否已在本地安装
const result = moduleInstallManager.getInstalledModule(moduleName);
if (result?.installStatus === moduleInstallManager.InstallStatus.INSTALLED) {
this.loadLivePage(); // 已安装,直接跳转
return;
}
// 2. 未安装,监听下载状态并触发下载
moduleInstallManager.on('moduleInstallStatus', (data) => {
if (data.taskStatus === moduleInstallManager.TaskStatus.INSTALL_SUCCESSFUL) {
hilog.info(0x0000, 'TAG', '直播模块下载成功');
this.loadLivePage(); // 下载完成,跳转页面
}
});
try {
// 触发按需下载
await moduleInstallManager.fetchModules([moduleName]);
} catch (err) {
hilog.error(0x0000, 'TAG', `模块下载失败: ${err.message}`);
}
}
private loadLivePage() {
// 动态导入并使用 Feature HAP 中的组件或路由
import('feature_live').then((ns) => {
hilog.info(0x0000, 'TAG', '成功加载直播模块');
});
}
build() {
Column() {
Button('进入直播间 (按需下载)')
.onClick(() => this.enterLiveRoom())
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
二、 真正的“动态下发”机制:基于 Feature HAP 的动态更新
如果你的目标是实现类似“不发版即可更新功能”的热修复或动态下发,鸿蒙原生提供了基于 DynamicModule 的机制。其底层原理是增量加载与运行时模块热插拔。
1. 核心工作流程
- 独立编译:将需要动态下发的功能模块编译为独立的 Feature HAP。
- 云端托管:将编译好的模块包(如
.hap或.hsp文件)托管至云端服务器。 - 按需拉取:主应用通过
ModuleManager检查模块状态,若本地不存在或需更新,则从云端下载。 - 动态安装与调用:下载完成后,通过系统接口安装模块,主应用即可调用新下发的功能。
2. 实战代码示例
步骤一:主应用中动态加载并调用
在主模块的 MainAbility 中,通过 ModuleManager 管理动态模块的生命周期:
// mainapp/src/main/ets/MainAbility.ets
import { ModuleManager } from '@kit.AbilityKit'; // 假设引入系统模块管理器
export default class MainAbility extends UIAbility {
private moduleManager: ModuleManager | null = null;
onCreate() {
this.moduleManager = ModuleManager.getInstance(this.context);
this.loadDynamicFeature();
}
private async loadDynamicFeature() {
const moduleName = 'dynamic-feature';
try {
// 1. 检查动态模块是否已安装/可用
const isAvailable = await this.moduleManager?.isModuleAvailable(moduleName);
if (!isAvailable) {
// 2. 若不可用,从云端服务器拉取并安装
const downloadUrl = 'https://your-server.com/dynamic-feature.hap';
await this.moduleManager?.installModule(downloadUrl, {
onInstalled: (name) => {
console.info(`模块 ${name} 动态安装成功`);
this.callDynamicFunction(); // 安装后执行业务逻辑
},
onFailed: (err) => {
console.error(`模块安装失败: ${err.message}`);
}
});
} else {
// 3. 若已存在,直接调用
this.callDynamicFunction();
}
} catch (e) {
console.error('动态模块加载异常:', e);
}
}
private callDynamicFunction() {
// 调用已加载的动态模块中的服务或页面
console.info('执行动态下发的新功能逻辑');
}
}
3. 补充说明:集成态 HSP 的跨应用共享
虽然 HSP 不支持运行时热更新,但鸿蒙提供了集成态 HSP 机制,用于解决“同一组织内部多个应用之间”的代码与资源共享问题:
- 解耦包名:在构建和发布过程中,集成态 HSP 不与特定的宿主应用包名(bundleName)强耦合。
- 自动替换与重签:工具链在集成时,会自动将 HSP 的包名替换为当前宿主应用的包名,并重新签名生成新的 HSP 包。
- 场景:适用于企业级矩阵 App,多个 App 共用同一套底层基础库,但各自独立发版更新。
三、 包体极致优化:基于 HSP 的按需加载策略
随着应用业务扩展,首包体积过大不仅增加用户的下载等待时间,还会拖慢冷启动速度。HDC 2026 发布的“按需加载”能力,正是基于 HSP 的动态链接特性实现的。
- 传统痛点:在传统单 HAP 或纯 HAR 静态链接模式下,公共库会被物理拷贝到各个业务模块中,导致包体积线性膨胀。
- HSP 破局:将非核心的大型业务模块抽离为 HSP。用户首次从应用市场下载时,仅安装包含核心功能的 Entry HAP(首包体积可精简 50% 以上)。当用户点击特定功能(如直播、AR 试穿)时,系统再从云端动态拉取对应的业务 HSP 并加载到内存。
- 收益:既打破了传统模式下的包体积膨胀,又实现了系统级安全管控与强完整性校验。
我们将“直播”功能抽离为一个独立的 HSP 模块(例如命名为 feature_live),使其不再随主应用安装,而是按需动态加载。
步骤一:创建 HSP 模块并导出能力
在 feature_live 模块中,编写直播相关的 UI 组件和业务逻辑,并通过 index.ets 统一导出:
// feature_live/src/main/ets/components/LiveRoom.ets
@Component
export struct LiveRoom {
@Prop roomId: string;
build() {
Column() {
Text(`直播间: ${this.roomId}`).fontSize(24)
// 复杂的直播推流/拉流 UI 组件
}
}
}
// feature_live/index.ets (模块入口)
export { LiveRoom } from './src/main/ets/components/LiveRoom';
步骤二:主模块(Entry)按需动态加载
在主应用中,不直接静态 import 该 HSP,而是在用户触发直播入口时,通过路由或动态导入机制加载:
// entry/src/main/ets/pages/HomePage.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct HomePage {
build() {
Column() {
Button('进入直播间 (按需加载)')
.onClick(() => {
// 利用鸿蒙的 @bundle 协议,跳转到 HSP 模块中的页面
// 格式:@bundle:包名/模块名/路径/页面文件名
router.pushUrl({
url: '@bundle:com.example.myshop/feature_live/ets/pages/LivePage'
}).catch((err) => {
console.error('直播模块尚未下载或加载失败:', err.message);
});
})
}
}
}
步骤三:配置按需下载(结合 Feature HAP 动态下发)
如果希望该 HSP 模块在用户首次点击时从云端实时拉取,可将其配置为 Feature HAP,在 module.json5 中关闭随包安装:
// feature_live/module.json5
{
"module": {
"name": "feature_live",
"type": "feature",
"deliveryWithInstall": false // 核心配置:不随主包安装,用户点击时动态拉取
}
}
四、 高阶架构:集成态 HSP 与跨应用复用
在大型企业的 App 矩阵中,多个应用(如电商 App、外卖 App、打车 App)往往需要复用同一套底层网络库或 UI 组件。
- 应用内 HSP:编译时与宿主包名(bundleName)强耦合,仅限当前应用使用。
- 集成态 HSP(进阶):在构建和发布过程中,不与特定应用包名耦合。当其他宿主应用集成该 HSP 时,工具链会自动将 HSP 的包名替换为宿主应用的包名,并重新签名生成新的 HSP 包。这极大降低了企业级多应用协同开发的维护成本。
1. 核心配置:开启集成态 HSP
在基础工程(如网络库、UI 组件库)的模块级配置文件 build-profile.json5 中,通过开启 integratedHsp 选项,将普通的 HSP 转换为集成态 HSP:
// 基础库模块 build-profile.json5
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
"integratedHsp": true // 核心配置:开启集成态 HSP
}
}
}
2. 工程级标准化配置
由于集成态 HSP 在打包时会自动替换宿主包名并重新签名,为了保证多应用间的兼容性和避免安装冲突,必须在所有相关工程的工程级 build-profile.json5 中开启标准化 URL 拼接:
// 工程级 build-profile.json5
{
"app": {
"products": [
{
"buildOption": {
"strictMode": {
"useNormalizedOHMUrl": true // 必须开启,否则集成态 HSP 无法正确解析
}
}
}
]
}
}
五、 HAR 与 HSP 混用的性能陷阱
在实际工程中,如果多个 HAP 或 HSP 模块同时引用了同一个 HAR(静态共享包),会引发严重的性能问题。
- 单例失效与重复执行:由于 HAR 是编译时物理拷贝,当 HAP 和 HSP 同时依赖同一个 HAR 时,HAR 中的代码会在两个模块加载时各执行一次。这会破坏单例模式,导致初始化逻辑(如耗时计算、文件读取)被执行两次,严重拖慢冷启动性能。
- 优化方案:从性能角度出发,若某个公共模块被多个 HAP/HSP 频繁引用,应将其从 HAR 转换为 HSP,确保运行时只保留一份物理实例。
1、 痛点复现:HAR 单例失效与性能损耗
当 HAP 和 HSP 模块同时引用同一个 HAR 时,由于 HAR 是静态编译,其内部的模块级代码会被执行多次,导致耗时操作重复。
1. 共享工具类(HAR)
// har_common/src/main/ets/utils/Utils.ets
const LARGE_NUMBER = 100000000;
function func(): number {
let count = 0;
while (count < LARGE_NUMBER) { count++; } // 模拟耗时初始化逻辑
return count;
}
// 模块级变量,在 HAR 被加载时执行
export let funcResult = func();
2. 错误引用方式(导致冷启动极慢)
// entry (HAP) 中引用
import { funcResult } from 'har_common';
// hsp_library (HSP) 中引用
import { funcResult } from 'har_common';
⚠️ 性能影响:由于单例模式被破坏,func() 会在两个模块加载时各执行一次,实测可能导致冷启动耗时从 853ms 飙升至 3125ms。
2、 优化方案:将 HAR 转换为 HSP
从性能角度出发,若 HSP 和 HAR 均能满足业务需求,建议将 HSP 改为 HAR,或者直接将公共 HAR 升级为 HSP。以下是 HAR 转 HSP 的标准化配置步骤:
1. 修改模块级 module.json5
将模块类型改为 shared,并添加随应用安装下发的配置:
// har_common/module.json5
{
"module": {
"type": "shared", // 核心修改:由 har 改为 shared
"deliveryWithInstall": true // 核心修改:添加此字段
}
}
2. 修改模块级构建脚本 hvigorfile.ts
将构建任务由 HAR 任务替换为 HSP 任务:
// har_common/hvigorfile.ts
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, // 核心修改:由 harTasks 改为 hspTasks
}
3. 清理构建配置 build-profile.json5
删除仅 HAR 支持的混淆规则配置项:
// har_common/build-profile.json5
{
// 删除以下配置项:
// "consumerFiles": "./consumer-rules.txt"
}
六、 多模块路由:HSP 页面的跨模块跳转
在动态特性架构下,主模块(Entry)通常需要跳转到按需加载的 HSP 模块中的页面。由于 HSP 是动态加载的,不能使用传统的硬编码路由。
鸿蒙提供了基于 @bundle 协议的标准化跳转方案:
import { router } from '@kit.ArkUI';
// 跳转到 HSP 模块中的页面
Button('进入动态特性页面')
.onClick(() => {
router.pushUrl({
// 格式:@bundle:包名(bundleName)/模块名(moduleName)/路径/页面文件名(不加.ets后缀)
url: '@bundle:com.example.myapp/library/ets/pages/DynamicPage'
}).then(() => {
console.info('成功跳转到 HSP 动态页面');
}).catch((err) => {
console.error(`跳转失败,可能是 HSP 尚未按需下载: ${err.message}`);
});
})
架构总结:
在鸿蒙的动态特性架构中,HSP 负责“降本增效”(减小包体积、跨应用复用),而 Feature HAP 负责“业务扩展”(按需下载、热更新)。在大型工程中,合理划分两者的边界,并规避 HAR 混用带来的性能陷阱,是构建高可用、高性能鸿蒙原生应用的关键。
更多推荐



所有评论(0)