在鸿蒙(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 混用带来的性能陷阱,是构建高可用、高性能鸿蒙原生应用的关键。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐