Administrator
发布于 2026-09-03 / 6,659 阅读
0
0

鸿蒙热修复体系:动态模块加载与安全更新机制

鸿蒙百篇系列第 75 篇 · 专家

第 74 篇讲了灰度发布。灰度能控制风险,但有一个限制:发现问题到修复版本上架,走 AGC 审核至少要一两天。有些 Bug 等不起——闪退率超过 1%、支付页面打不开、核心功能不可用。这时候需要的是"不发版就修好"。

鸿蒙不支持传统意义上的"热修复"(替换已编译的方法体),但提供了动态模块加载能力,可以做到接近热修复的效果。这篇讲清楚鸿蒙能做什么、不能做什么、怎么做。

一、鸿蒙能做的"热修复"

先说清楚边界。以下三种方式按"侵入性从低到高"排列:

方式

原理

能修什么

不能修什么

审核要求

远程配置降级

服务端下发开关

功能开关、UI 样式

逻辑 Bug

不需要

HSP 动态模块

服务端下发 .hsp 包

整个模块的逻辑

入口模块

需审核

Feature HAP 按需更新

AGC 推送新 feature

feature 模块

entry 模块

需审核

方式一:远程配置降级

最轻量的修复方式。不是真的修 Bug,而是把有 Bug 的功能关掉。

// 应用启动时拉取配置
async function initRemoteConfig() {
  try {
    const config = await httpClient.get<Record<string, boolean>>('/api/config');
    AppStorage.setOrCreate('featureFlags', config);
  } catch (err) {
    // 网络失败时用本地默认值
    AppStorage.setOrCreate('featureFlags', { enable_live: true, enable_pay: true });
  }
}

// 业务代码根据开关决定走哪条路径
@Entry
@Component
struct HomePage {
  @StorageLink('featureFlags') flags: Record<string, boolean> = {};

  build() {
    Column() {
      if (this.flags['enable_live']) {
        LiveEntryComponent()  // 正常逻辑
      } else {
        MaintenanceComponent()  // 降级提示
      }
    }
  }
}

优点:不需要审核,配置改了用户下次启动就生效。
缺点:只能关功能,不能修逻辑。Bug 严重的模块可以关,但不影响主流程的 Bug 关不了。

方式二:HSP 动态模块

HSP(Harmony Shared Package)是运行时加载的共享包。可以把有 Bug 的模块做成 HSP,修复后重新编译 HSP 包,通过服务端下发给用户。

第 68 篇讲过包管理机制,HSP 是鸿蒙的三种模块形态之一。关键特性是:HSP 可以独立于主包更新,不需要整个应用重新安装。

实现步骤:

第一步:将有问题的模块配置为 HSP

在模块的 oh-package.json5 或 build-profile.json5 中配置:

{
  "name": "feature_payment",
  "type": "shared",
  "description": "支付模块"
}

第二步:定义模块版本

// feature_payment/oh-package.json5
{
  "name": "feature_payment",
  "version": "1.0.1",  // 修复后升版本号
  "main": "index.ets"
}

第三步:应用启动时检查模块版本

import { bundleManager } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';

async function checkModuleUpdate() {
  // 获取当前安装的模块信息
  const bundleInfo = await bundleManager.getBundleInfo('com.example.app',
    bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE);

  // 检查支付模块版本
  const currentVersion = getModuleVersion(bundleInfo, 'feature_payment');
  const latestVersion = await fetchServerModuleVersion('feature_payment');

  if (compareVersion(currentVersion, latestVersion) < 0) {
    // 需要更新
    await downloadAndUpdateModule('feature_payment', latestVersion);
  }
}

async function downloadAndUpdateModule(moduleName: string, version: string) {
  // 1. 从服务端下载 HSP 包
  const downloadUrl = `https://cdn.example.com/modules/${moduleName}_${version}.hsp`;
  const filePath = await downloadFile(downloadUrl);

  // 2. 安装 HSP 包(需要系统权限或用户确认)
  // 注意:应用内安装 HSP 需要通过系统接口,具体可用性取决于应用权限
  try {
    await installHspModule(filePath);
    console.info('模块更新成功');
  } catch (err) {
    console.error('模块更新失败: ' + (err as Error).message);
    // 降级处理
  }
}

第四步:动态加载模块

import { router } from '@kit.ArkUI';

// 跳转到 HSP 模块中的页面
function gotoPaymentPage() {
  router.pushUrl({
    url: '@bundle:com.example.app/feature_payment/ets/pages/PaymentPage'
  }).then(() => {
    console.info('跳转支付页面成功');
  }).catch((err: BusinessError) => {
    console.error('跳转失败: ' + err.message);
  });
}

方式三:Feature HAP 按需更新

Feature HAP 是按需安装的模块(参考第 68 篇)。配置 deliveryWithInstall: false 的 feature 模块不会随主包安装,而是用户需要时才下载。

// feature_live/module.json5
{
  "module": {
    "name": "feature_live",
    "type": "feature",
    "deliveryWithInstall": false,  // 不随主包安装
    "installationFree": false,
    "deviceTypes": ["phone"],
    "abilities": [
      {
        "name": "LiveAbility",
        "srcEntry": "./ets/liveability/LiveAbility.ets"
      }
    ]
  }
}

更新流程:修复 Bug → 编译新版本 feature HAP → 上传 AGC → AGC 分发给已安装该 feature 的用户。

二、模块拆分原则

不是所有模块都适合做热修复。拆分原则:

适合做 HSP 的模块

不适合的模块

独立业务功能(如直播、社区)

应用入口(entry)

变更频率高的模块

核心框架(common)

非主流程依赖的模块

被其他模块依赖的基础模块

纯逻辑模块(无 Native 代码)

含 C++ 的模块(需要匹配 .so)

一个模块要能热更新,它和主包之间的耦合度要低。如果支付模块直接引用了 common 层的类型定义,HSP 更新时 common 层也要兼容。否则旧 common + 新 HSP 会出现类型不匹配。

接口隔离

HSP 模块和主包之间通过接口通信,不直接引用内部实现:

// common 层定义接口
export interface IPaymentService {
  pay(orderId: string, amount: number): Promise<PaymentResult>;
  checkPaymentStatus(orderId: string): Promise<PaymentStatus>;
}

// feature_payment 模块实现接口
export class PaymentServiceImpl implements IPaymentService {
  async pay(orderId: string, amount: number): Promise<PaymentResult> {
    // 实际支付逻辑
  }

  async checkPaymentStatus(orderId: string): Promise<PaymentStatus> {
    // 查询逻辑
  }
}

主包通过接口调用,不关心实现是哪个版本。HSP 更新时只要接口签名不变,主包不需要改。

三、版本兼容性管理

HSP 动态更新最怕的是版本不兼容。新版 HSP 用了新接口,旧版主包调不了;旧版 HSP 被新版主包调用,缺了新参数。

版本协商机制

// HSP 模块导出版本信息
export const MODULE_VERSION = '1.2.0';
export const SUPPORTED_API_VERSIONS = ['1.0', '1.1', '1.2'];

// 主包调用前检查兼容性
function checkCompatibility(hspModule: any): boolean {
  const supportedVersions: string[] = hspModule.SUPPORTED_API_VERSIONS;
  const requiredVersion = '1.1';
  return supportedVersions.includes(requiredVersion);
}

async function callPayment(hspModule: any, orderId: string, amount: number) {
  if (!checkCompatibility(hspModule)) {
    // 不兼容,降级到旧逻辑或提示用户更新
    throw new Error('支付模块版本不兼容,请更新应用');
  }
  return hspModule.pay(orderId, amount);
}

向下兼容原则

HSP 模块的接口设计必须遵守向下兼容:

变更类型

是否兼容

说明

新增方法

兼容

旧调用方不受影响

新增可选参数

兼容

旧调用方传少参数不报错

删除方法

不兼容

旧调用方会找不到

修改参数类型

不兼容

旧调用方传错类型

修改返回值结构

不兼容

旧调用方解析失败

修复 Bug 时只能改实现,不能改接口。改接口等于改了契约,必须整个应用发新版。

四、安全更新机制

热修复涉及从服务端下载代码包并加载,安全性是核心问题。

HSP 包签名校验

import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { fs } from '@kit.CoreFileKit';

async function verifyHspSignature(filePath: string, expectedHash: string): Promise<boolean> {
  // 1. 读取 HSP 文件
  const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
  const stat = fs.statSync(filePath);
  const buffer = new ArrayBuffer(stat.size);
  fs.readSync(file.fd, buffer);
  fs.closeSync(file);

  // 2. 计算 SHA-256 摘要
  const md = cryptoFramework.createMd('SHA256');
  md.update({ data: buffer });
  const digest = md.digest();
  const actualHash = Array.from(new Uint8Array(digest.data))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  // 3. 对比预期摘要
  return actualHash === expectedHash;
}

async function downloadAndUpdateModule(moduleName: string, version: string) {
  const downloadUrl = `https://cdn.example.com/modules/${moduleName}_${version}.hsp`;

  // 从服务端获取预期哈希值(通过 HTTPS)
  const expectedHash = await fetchModuleHash(moduleName, version);

  // 下载 HSP 文件
  const filePath = `${context.filesDir}/${moduleName}_${version}.hsp`;
  await downloadFile(downloadUrl, filePath);

  // 验签
  const isValid = await verifyHspSignature(filePath, expectedHash);
  if (!isValid) {
    throw new Error('模块签名校验失败,可能被篡改');
  }

  // 验签通过后安装
  await installHspModule(filePath);
}

更新流程安全检查清单

检查项

说明

下载走 HTTPS

防止传输中间人篡改

服务端返回预期哈希

用独立接口获取,不放在下载响应里

客户端校验文件摘要

下载后计算 SHA256 对比

版本号校验

不允许降级安装(防止重放攻击)

安装失败有降级

安装失败不影响已有功能

五、热修复适用场景

场景

推荐方式

说明

支付页面闪退

HSP 模块更新

不能等审核,立即下发修复

直播模块白屏

HSP 模块更新

非核心功能,可独立更新

首页 Banner 显示错位

远程配置

UI 问题,改配置即可

登录接口变更

Feature HAP 更新

接口变了,需要改逻辑

entry 模块崩溃

无法热修复

只能走灰度发新版

六、降级和容错

热修复失败时不能影响用户正常使用:

async function safeLoadModule(moduleName: string): Promise<any> {
  try {
    // 尝试加载最新 HSP
    const module = await loadHspModule(moduleName);
    return module;
  } catch (err) {
    console.warn('HSP 加载失败,使用内置版本: ' + (err as Error).message);
    // 降级使用打包时内置的旧版本
    try {
      const fallback = await loadBuiltinModule(moduleName);
      return fallback;
    } catch (fallbackErr) {
      // 内置版也挂了,返回空实现
      return createNoopModule(moduleName);
    }
  }
}

// 空实现,防止调用报错
function createNoopModule(moduleName: string): any {
  return new Proxy({}, {
    get: () => () => {
      throw new Error(`模块 ${moduleName} 不可用,请更新应用`);
    }
  });
}

七、小结

鸿蒙的热修复本质上是"动态模块加载 + 版本协商 + 安全校验"。你不能像 Android 那样直接替换一个方法的字节码,但可以通过合理的模块拆分,让需要热修复的部分变成可独立更新的 HSP 模块。

前提是在架构设计阶段(第 71 篇)就把模块边界划清楚。等出了 Bug 再想热修复,发现代码全塞在 entry 里,那就只能走灰度发新版。

下一篇讲跨平台工程实践——从纯鸿蒙应用到多端复用的选型决策。


评论