HarmonyOS APP开发中的应用更新:OTA更新、增量更新与热修复全链路实战

举报
Jack20 发表于 2026/06/20 18:28:33 2026/06/20
【摘要】 HarmonyOS APP开发中的应用更新:OTA更新、增量更新与热修复全链路实战📌 核心要点:掌握 @ohos.update 的 OTA 全量/增量更新机制,理解热修复的原理与限制,实现版本检测、静默更新与强制更新的完整方案。 一、背景与动机你刚上线了一个新版本,修复了一个关键 Bug,但三天过去了,还有 30% 的用户停留在旧版本。他们不断投诉"怎么还有这个问题",你只能无奈地回复"...

HarmonyOS APP开发中的应用更新:OTA更新、增量更新与热修复全链路实战

📌 核心要点:掌握 @ohos.update 的 OTA 全量/增量更新机制,理解热修复的原理与限制,实现版本检测、静默更新与强制更新的完整方案。


一、背景与动机

你刚上线了一个新版本,修复了一个关键 Bug,但三天过去了,还有 30% 的用户停留在旧版本。他们不断投诉"怎么还有这个问题",你只能无奈地回复"请更新到最新版本"。这种"更新焦虑"每个开发者都经历过。

应用更新看似简单——下载新包、安装、重启——但在 HarmonyOS 的生态中,它远比你想的复杂。全量更新包太大,几百 MB 的下载量让用户望而却步;增量更新虽然小,但差分算法有版本限制;热修复听起来很美好,但能修的范围有限;强制更新又容易引发用户反感……

更关键的是,HarmonyOS 的应用更新机制与 Android 有本质区别。它不依赖 Google Play 的更新服务,而是有自己的 OTA 框架。理解这个框架的工作原理,是做好应用更新的前提。


二、核心原理

2.1 应用更新方式对比

graph TB
    A[应用更新方式] --> B[OTA全量更新]
    A --> C[OTA增量更新]
    A --> D[热修复]
    
    B --> B1[下载完整HAP包]
    B1 --> B2[替换安装]
    B2 --> B3[重启应用]
    
    C --> C1[下载差分包]
    C1 --> C2[本地合成新包]
    C2 --> C3[校验完整性]
    C3 --> C4[替换安装]
    
    D --> D1[下载补丁文件]
    D1 --> D2[动态加载补丁]
    D2 --> D3[无需重启]
    
    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 primary
    class B,B1,B2,B3 info
    class C,C1,C2,C3,C4 warning
    class D,D1,D2,D3 purple

三种更新方式对比:

特征 OTA全量更新 OTA增量更新 热修复
更新包大小 完整HAP(大) 差分包(小) 补丁文件(极小)
下载耗时 长 短 极短
是否需要重启 是 是 否
修复范围 任意修改 任意修改 仅逻辑修复
版本限制 无 需从指定版本升级 需预先集成框架
安全性 高(完整校验) 高(合成后校验) 中(需信任补丁)
类比 换整辆车 换发动机 换机油

2.2 OTA 更新完整流程

sequenceDiagram
    participant App as 应用
    participant Server as 更新服务器
    participant Update as @ohos.update
    participant System as 系统
    
    App->>Server: 请求版本信息(当前版本号+包名)
    Server-->>App: 返回更新信息(新版本号+下载地址+更新策略)
    
    alt 有新版本
        App->>App: 判断更新策略(静默/提示/强制)
        
        alt 静默更新
            App->>Update: download() 下载更新包
        else 提示更新
            App->>App: 显示更新提示弹窗
            alt 用户同意
                App->>Update: download() 下载更新包
            else 用户拒绝
                App->>App: 记录拒绝,稍后重试
            end
        else 强制更新
            App->>App: 显示强制更新弹窗(不可关闭)
            App->>Update: download() 下载更新包
        end
        
        Update->>Server: 下载更新包
        Server-->>Update: 返回更新包数据
        Update-->>App: 下载进度回调
        
        Update->>Update: 校验包完整性
        Update->>System: upgrade() 安装更新
        System-->>App: 安装完成回调
        App->>App: 重启应用
    else 无新版本
        App->>App: 当前已是最新版本
    end
    
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F2,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff

2.3 增量更新原理

增量更新的核心是差分算法:将新版本和旧版本进行二进制差分,只传输差异部分。

旧版本 (v1.0.0)          新版本 (v1.1.0)
┌──────────────┐         ┌──────────────┐
│  HAP 包 A    │   →→→   │  HAP 包 B    │
└──────────────┘         └──────────────┘
         │                        │
         └──────── bsdiff ────────┘
                    │
           ┌──────────────┐
           │  差分包 Δ     │  (仅包含差异部分)
           │  大小远小于B  │
           └──────────────┘
                    │
         客户端: A + Δ = B (合成新版本)

限制:增量更新要求客户端必须是差分时指定的基准版本。如果用户跳过了中间版本(如从 v1.0 直接升到 v1.2),差分包可能无法合成。


三、代码实战

3.1 版本检测与更新管理器

import { update } from '@kit.UpdateKit';
import { bundleManager } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG = '[UpdateManager]';

/**
 * 更新策略枚举
 */
export enum UpdateStrategy {
  SILENT = 0,      // 静默更新:后台下载,下次启动生效
  PROMPT = 1,      // 提示更新:弹窗提示用户
  FORCED = 2       // 强制更新:必须更新才能使用
}

/**
 * 版本信息模型
 */
export interface VersionInfo {
  versionCode: number;
  versionName: string;
  versionDescription: string;
  downloadUrl: string;
  packageSize: number;
  md5: string;
  strategy: UpdateStrategy;
  isIncremental: boolean;
  baseVersionCode: number; // 增量更新的基准版本
}

/**
 * 更新状态枚举
 */
export enum UpdateStatus {
  IDLE = 'idle',
  CHECKING = 'checking',
  DOWNLOADING = 'downloading',
  DOWNLOAD_PAUSED = 'paused',
  DOWNLOAD_COMPLETE = 'downloaded',
  INSTALLING = 'installing',
  INSTALL_COMPLETE = 'installed',
  ERROR = 'error'
}

/**
 * 应用更新管理器
 * 封装版本检测、下载、安装的完整流程
 */
export class AppUpdateManager {
  private static instance: AppUpdateManager;
  private updater: update.Updater | null = null;
  private currentVersionCode: number = 0;
  private currentVersionName: string = '';

  // 更新状态回调
  private onStatusChange?: (status: UpdateStatus, progress: number) => void;

  private constructor() {
    this.loadCurrentVersion();
  }

  static getInstance(): AppUpdateManager {
    if (!AppUpdateManager.instance) {
      AppUpdateManager.instance = new AppUpdateManager();
    }
    return AppUpdateManager.instance;
  }

  /**
   * 加载当前版本信息
   */
  private loadCurrentVersion() {
    try {
      const bundleInfo = bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
      );
      this.currentVersionCode = bundleInfo.versionCode;
      this.currentVersionName = bundleInfo.versionName;
      hilog.info(0x0001, TAG, `当前版本: ${this.currentVersionName}(${this.currentVersionCode})`);
    } catch (err) {
      hilog.error(0x0001, TAG, `获取版本信息失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 注册状态回调
   */
  setOnStatusChange(callback: (status: UpdateStatus, progress: number) => void) {
    this.onStatusChange = callback;
  }

  /**
   * 检查更新
   * 向服务器请求最新版本信息
   */
  async checkUpdate(): Promise<VersionInfo | null> {
    this.notifyStatus(UpdateStatus.CHECKING, 0);

    try {
      // 向更新服务器请求版本信息(示意代码)
      // 实际项目中应调用自建更新服务或应用市场API
      const versionInfo = await this.fetchVersionFromServer();

      if (!versionInfo) {
        hilog.info(0x0001, TAG, '当前已是最新版本');
        this.notifyStatus(UpdateStatus.IDLE, 0);
        return null;
      }

      // 判断是否需要更新
      if (versionInfo.versionCode <= this.currentVersionCode) {
        hilog.info(0x0001, TAG, '当前已是最新版本');
        this.notifyStatus(UpdateStatus.IDLE, 0);
        return null;
      }

      // 判断增量更新是否可用
      if (versionInfo.isIncremental) {
        if (this.currentVersionCode !== versionInfo.baseVersionCode) {
          hilog.warn(0x0001, TAG, '增量更新基准版本不匹配,将使用全量更新');
          versionInfo.isIncremental = false;
        }
      }

      hilog.info(0x0001, TAG, `发现新版本: ${versionInfo.versionName}`);
      return versionInfo;
    } catch (err) {
      hilog.error(0x0001, TAG, `检查更新失败: ${JSON.stringify(err)}`);
      this.notifyStatus(UpdateStatus.ERROR, 0);
      return null;
    }
  }

  /**
   * 下载更新包
   */
  async downloadUpdate(versionInfo: VersionInfo): Promise<boolean> {
    this.notifyStatus(UpdateStatus.DOWNLOADING, 0);

    try {
      // 创建 Updater 实例
      this.updater = update.getOnlineUpdater({
        vendor: 'com.example.app',
        moduleName: 'entry'
      });

      // 订阅下载进度
      this.updater.on('downloadProgress', (progress: update.ProgressInfo) => {
        const percent = Math.floor(progress.percent * 100);
        hilog.info(0x0001, TAG, `下载进度: ${percent}%`);
        this.notifyStatus(UpdateStatus.DOWNLOADING, percent);
      });

      // 订阅下载错误
      this.updater.on('downloadError', (error: update.UpdateResult) => {
        hilog.error(0x0001, TAG, `下载失败: ${error.errorCode}`);
        this.notifyStatus(UpdateStatus.ERROR, 0);
      });

      // 构建下载请求
      const updateInfo: update.UpdateInfo = {
        version: versionInfo.versionName,
        size: versionInfo.packageSize,
        downloadUrl: versionInfo.downloadUrl
      };

      // 执行下载
      await this.updater.download(updateInfo);

      hilog.info(0x0001, TAG, '下载完成');
      this.notifyStatus(UpdateStatus.DOWNLOAD_COMPLETE, 100);
      return true;
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG, `下载更新失败: ${error.code} - ${error.message}`);
      this.notifyStatus(UpdateStatus.ERROR, 0);
      return false;
    }
  }

  /**
   * 安装更新
   */
  async installUpdate(): Promise<boolean> {
    this.notifyStatus(UpdateStatus.INSTALLING, 0);

    try {
      if (!this.updater) {
        hilog.error(0x0001, TAG, 'Updater未初始化');
        return false;
      }

      // 订阅安装进度
      this.updater.on('upgradeProgress', (progress: update.ProgressInfo) => {
        const percent = Math.floor(progress.percent * 100);
        hilog.info(0x0001, TAG, `安装进度: ${percent}%`);
        this.notifyStatus(UpdateStatus.INSTALLING, percent);
      });

      // 执行安装
      await this.updater.upgrade();

      hilog.info(0x0001, TAG, '安装完成');
      this.notifyStatus(UpdateStatus.INSTALL_COMPLETE, 100);
      return true;
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG, `安装更新失败: ${error.code} - ${error.message}`);
      this.notifyStatus(UpdateStatus.ERROR, 0);
      return false;
    }
  }

  /**
   * 取消下载
   */
  async cancelDownload(): Promise<void> {
    try {
      if (this.updater) {
        // 取消下载
        this.updater.off('downloadProgress');
        this.updater.off('downloadError');
      }
      this.notifyStatus(UpdateStatus.DOWNLOAD_PAUSED, 0);
      hilog.info(0x0001, TAG, '下载已取消');
    } catch (err) {
      hilog.error(0x0001, TAG, `取消下载失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 从服务器获取版本信息(示意代码)
   */
  private async fetchVersionFromServer(): Promise<VersionInfo | null> {
    // 实际项目中应调用自建更新服务API
    // 返回格式示例:
    return {
      versionCode: 2000000,
      versionName: '2.0.0',
      versionDescription: '1. 全新UI设计\n2. 修复已知Bug\n3. 性能优化',
      downloadUrl: 'https://update.example.com/app-v2.0.0.hap',
      packageSize: 50 * 1024 * 1024, // 50MB
      md5: 'abc123def456',
      strategy: UpdateStrategy.PROMPT,
      isIncremental: false,
      baseVersionCode: 0
    };
  }

  /**
   * 通知状态变更
   */
  private notifyStatus(status: UpdateStatus, progress: number) {
    if (this.onStatusChange) {
      this.onStatusChange(status, progress);
    }
  }

  /**
   * 获取当前版本信息
   */
  getCurrentVersion(): { code: number; name: string } {
    return {
      code: this.currentVersionCode,
      name: this.currentVersionName
    };
  }
}

3.2 更新 UI 交互页面

import { AppUpdateManager, UpdateStrategy, UpdateStatus, VersionInfo } from './AppUpdateManager';

@Entry
@Component
struct AppUpdatePage {
  // 更新管理器
  private updateManager: AppUpdateManager = AppUpdateManager.getInstance();

  // 状态
  @State currentVersion: string = '';
  @State updateStatus: UpdateStatus = UpdateStatus.IDLE;
  @State downloadProgress: number = 0;
  @State versionInfo: VersionInfo | null = null;
  @State showUpdateDialog: boolean = false;
  @State showForceDialog: boolean = false;
  @State errorMessage: string = '';

  aboutToAppear() {
    // 获取当前版本
    const version = this.updateManager.getCurrentVersion();
    this.currentVersion = version.name;

    // 注册状态回调
    this.updateManager.setOnStatusChange((status, progress) => {
      this.updateStatus = status;
      this.downloadProgress = progress;
    });

    // 自动检查更新
    this.checkForUpdate();
  }

  /**
   * 检查更新
   */
  private async checkForUpdate() {
    const versionInfo = await this.updateManager.checkUpdate();
    if (versionInfo) {
      this.versionInfo = versionInfo;

      // 根据更新策略显示不同的UI
      if (versionInfo.strategy === UpdateStrategy.FORCED) {
        this.showForceDialog = true;
      } else if (versionInfo.strategy === UpdateStrategy.PROMPT) {
        this.showUpdateDialog = true;
      } else {
        // 静默更新:直接下载
        this.startDownload();
      }
    }
  }

  /**
   * 开始下载
   */
  private async startDownload() {
    if (!this.versionInfo) return;
    await this.updateManager.downloadUpdate(this.versionInfo);
  }

  /**
   * 安装更新
   */
  private async installUpdate() {
    const success = await this.updateManager.installUpdate();
    if (success) {
      // 安装成功,提示用户重启
      this.showRestartPrompt();
    }
  }

  /**
   * 显示重启提示
   */
  private showRestartPrompt() {
    // 实际项目中应显示重启确认弹窗
    hilog.info(0x0001, 'UpdatePage', '更新安装完成,请重启应用');
  }

  build() {
    Column() {
      // 标题
      Text('应用更新')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 24 })

      // 当前版本信息
      Row() {
        Text('当前版本')
          .fontSize(14)
          .fontColor('#999999')
        Text(`v${this.currentVersion}`)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(16)
      .backgroundColor('#F5F5F5')
      .borderRadius(8)
      .margin({ bottom: 16 })

      // 更新状态展示
      this.UpdateStatusView()

      // 手动检查更新按钮
      if (this.updateStatus === UpdateStatus.IDLE) {
        Button('检查更新')
          .width('100%')
          .height(48)
          .backgroundColor('#4CAF50')
          .fontColor('#FFFFFF')
          .fontSize(16)
          .borderRadius(24)
          .margin({ top: 24 })
          .onClick(() => {
            this.checkForUpdate();
          })
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .bindContentCover(this.showUpdateDialog, this.UpdateDialog(), {
      modalTransition: ModalTransition.DEFAULT
    })
    .bindContentCover(this.showForceDialog, this.ForceUpdateDialog(), {
      modalTransition: ModalTransition.NONE // 强制更新不可关闭
    })
  }

  @Builder
  UpdateStatusView() {
    Column() {
      when (this.updateStatus) {
        // 空闲状态
        if (this.updateStatus === UpdateStatus.IDLE && !this.versionInfo) {
          Text('当前已是最新版本')
            .fontSize(16)
            .fontColor('#4CAF50')
            .fontWeight(FontWeight.Medium)
        }
        
        // 检查中
        if (this.updateStatus === UpdateStatus.CHECKING) {
          LoadingProgress()
            .width(40)
            .height(40)
            .color('#4CAF50')
          Text('正在检查更新...')
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 12 })
        }

        // 下载中
        if (this.updateStatus === UpdateStatus.DOWNLOADING) {
          Text(`正在下载更新 ${this.downloadProgress}%`)
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 12 })
          Progress({ value: this.downloadProgress, total: 100, type: ProgressType.Linear })
            .width('100%')
            .color('#4CAF50')
          Row() {
            Button('暂停')
              .height(36)
              .backgroundColor('#F5F5F5')
              .fontColor('#666666')
              .fontSize(14)
              .onClick(() => {
                this.updateManager.cancelDownload();
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.End)
          .margin({ top: 12 })
        }

        // 下载完成
        if (this.updateStatus === UpdateStatus.DOWNLOAD_COMPLETE) {
          Text('下载完成,准备安装')
            .fontSize(16)
            .fontColor('#4CAF50')
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 12 })
          Button('立即安装')
            .width('100%')
            .height(44)
            .backgroundColor('#4CAF50')
            .fontColor('#FFFFFF')
            .fontSize(16)
            .borderRadius(22)
            .onClick(() => {
              this.installUpdate();
            })
        }

        // 安装中
        if (this.updateStatus === UpdateStatus.INSTALLING) {
          LoadingProgress()
            .width(40)
            .height(40)
            .color('#4CAF50')
          Text(`正在安装更新 ${this.downloadProgress}%`)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 12 })
        }

        // 安装完成
        if (this.updateStatus === UpdateStatus.INSTALL_COMPLETE) {
          Text('✓ 更新安装完成')
            .fontSize(16)
            .fontColor('#4CAF50')
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 12 })
          Text('请重启应用以完成更新')
            .fontSize(14)
            .fontColor('#666666')
        }

        // 错误状态
        if (this.updateStatus === UpdateStatus.ERROR) {
          Text('✗ 更新失败')
            .fontSize(16)
            .fontColor('#F44336')
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 8 })
          Text(this.errorMessage || '请检查网络连接后重试')
            .fontSize(14)
            .fontColor('#999999')
            .margin({ bottom: 12 })
          Button('重试')
            .height(36)
            .backgroundColor('#4CAF50')
            .fontColor('#FFFFFF')
            .fontSize(14)
            .onClick(() => {
              this.checkForUpdate();
            })
        }
      }
    }
    .width('100%')
    .padding(20)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  UpdateDialog() {
    Column() {
      // 更新提示弹窗
      Text('发现新版本')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 8 })

      if (this.versionInfo) {
        Text(`v${this.versionInfo.versionName}`)
          .fontSize(16)
          .fontColor('#4CAF50')
          .margin({ bottom: 16 })

        Text(this.versionInfo.versionDescription)
          .fontSize(14)
          .fontColor('#666666')
          .lineHeight(22)
          .margin({ bottom: 24 })

        // 更新包大小提示
        Text(`更新包大小: ${(this.versionInfo.packageSize / 1024 / 1024).toFixed(1)}MB`)
          .fontSize(12)
          .fontColor('#999999')
          .margin({ bottom: 16 })
      }

      Row({ space: 12 }) {
        Button('稍后再说')
          .layoutWeight(1)
          .height(44)
          .backgroundColor('#F5F5F5')
          .fontColor('#666666')
          .fontSize(16)
          .borderRadius(22)
          .onClick(() => {
            this.showUpdateDialog = false;
          })
        Button('立即更新')
          .layoutWeight(1)
          .height(44)
          .backgroundColor('#4CAF50')
          .fontColor('#FFFFFF')
          .fontSize(16)
          .borderRadius(22)
          .onClick(() => {
            this.showUpdateDialog = false;
            this.startDownload();
          })
      }
    }
    .width('100%')
    .padding(24)
    .backgroundColor('#FFFFFF')
    .borderRadius(16)
  }

  @Builder
  ForceUpdateDialog() {
    Column() {
      // 强制更新弹窗(不可关闭)
      Text('⚠️ 需要更新')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FF9800')
        .margin({ bottom: 8 })

      Text('当前版本过旧,需要更新后才能继续使用')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ bottom: 16 })

      if (this.versionInfo) {
        Text(this.versionInfo.versionDescription)
          .fontSize(14)
          .fontColor('#666666')
          .lineHeight(22)
          .margin({ bottom: 24 })
      }

      Button('立即更新')
        .width('100%')
        .height(44)
        .backgroundColor('#FF9800')
        .fontColor('#FFFFFF')
        .fontSize(16)
        .borderRadius(22)
        .onClick(() => {
          this.showForceDialog = false;
          this.startDownload();
        })
    }
    .width('100%')
    .padding(24)
    .backgroundColor('#FFFFFF')
    .borderRadius(16)
  }
}

// 导入 hilog(上方代码中使用了)
import { hilog } from '@kit.PerformanceAnalysisKit';

3.3 增量更新与热修复方案

import { update } from '@kit.UpdateKit';
import { bundleManager } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG = '[IncrementalUpdate]';

/**
 * 增量更新管理器
 * 支持差分包下载和本地合成
 */
export class IncrementalUpdateManager {
  private static instance: IncrementalUpdateManager;

  static getInstance(): IncrementalUpdateManager {
    if (!IncrementalUpdateManager.instance) {
      IncrementalUpdateManager.instance = new IncrementalUpdateManager();
    }
    return IncrementalUpdateManager.instance;
  }

  /**
   * 检查增量更新是否可用
   * @param baseVersion 增量包的基准版本号
   * @returns 是否可以使用增量更新
   */
  canUseIncrementalUpdate(baseVersion: number): boolean {
    try {
      const bundleInfo = bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
      );
      const currentCode = bundleInfo.versionCode;

      // 只有当前版本与基准版本完全匹配时,增量更新才可用
      if (currentCode === baseVersion) {
        hilog.info(0x0001, TAG, '增量更新可用');
        return true;
      }

      hilog.warn(0x0001, TAG, `增量更新不可用: 当前${currentCode}, 基准${baseVersion}`);
      return false;
    } catch (err) {
      hilog.error(0x0001, TAG, `检查增量更新失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  /**
   * 下载并应用增量更新
   */
  async applyIncrementalUpdate(
    patchDownloadUrl: string,
    patchSize: number,
    onProgress?: (percent: number) => void
  ): Promise<boolean> {
    try {
      const updater = update.getOnlineUpdater({
        vendor: 'com.example.app',
        moduleName: 'entry'
      });

      // 订阅下载进度
      updater.on('downloadProgress', (progress: update.ProgressInfo) => {
        const percent = Math.floor(progress.percent * 100);
        hilog.info(0x0001, TAG, `差分包下载进度: ${percent}%`);
        if (onProgress) {
          onProgress(percent);
        }
      });

      // 下载差分包
      const updateInfo: update.UpdateInfo = {
        version: 'incremental',
        size: patchSize,
        downloadUrl: patchDownloadUrl
      };

      await updater.download(updateInfo);
      hilog.info(0x0001, TAG, '差分包下载完成');

      // 安装(系统会自动合成新版本)
      await updater.upgrade();
      hilog.info(0x0001, TAG, '增量更新安装完成');

      return true;
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG, `增量更新失败: ${error.code} - ${error.message}`);
      return false;
    }
  }
}

/**
 * 热修复管理器
 * 
 * ⚠️ 重要说明:
 * HarmonyOS 目前不原生支持类似 Android 的热修复框架(如 Tinker/Sophix)
 * 以下代码展示的是一种基于配置下发的"逻辑热修复"方案
 * 仅适用于修复业务逻辑错误,不能修改ArkTS代码本身
 */
export class HotFixManager {
  private static instance: HotFixManager;
  private fixConfig: Map<string, object> = new Map();

  static getInstance(): HotFixManager {
    if (!HotFixManager.instance) {
      HotFixManager.instance = new HotFixManager();
    }
    return HotFixManager.instance;
  }

  /**
   * 从服务器加载热修复配置
   * 配置中包含功能开关、参数覆盖、逻辑分支等
   */
  async loadHotFixConfig(): Promise<boolean> {
    try {
      // 从服务器获取修复配置(示意代码)
      const config = await this.fetchFixConfigFromServer();

      if (config) {
        // 解析并应用修复配置
        for (const [key, value] of Object.entries(config)) {
          this.fixConfig.set(key, value);
          // 写入 AppStorage,全局生效
          AppStorage.setOrCreate(key, value);
        }
        hilog.info(0x0001, TAG, `已加载 ${Object.keys(config).length} 项修复配置`);
        return true;
      }

      return false;
    } catch (err) {
      hilog.error(0x0001, TAG, `加载修复配置失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  /**
   * 获取修复配置值
   * 在业务代码中使用,实现逻辑热修复
   */
  getFixValue<T>(key: string, defaultValue: T): T {
    const value = this.fixConfig.get(key);
    if (value !== undefined) {
      return value as T;
    }
    return defaultValue;
  }

  /**
   * 检查功能开关
   * 通过配置下发控制功能的启用/禁用
   */
  isFeatureEnabled(featureKey: string): boolean {
    return this.getFixValue<boolean>(`feature_${featureKey}`, true);
  }

  /**
   * 获取修复后的参数
   * 通过配置下发覆盖错误的参数
   */
  getFixedParam(paramKey: string, defaultValue: string): string {
    return this.getFixValue<string>(`param_${paramKey}`, defaultValue);
  }

  /**
   * 从服务器获取修复配置(示意代码)
   */
  private async fetchFixConfigFromServer(): Promise<Record<string, object> | null> {
    // 实际项目中应调用自建修复配置服务
    // 返回格式示例:
    return {
      // 修复:禁用有Bug的功能
      'feature_advanced_edit': false,
      // 修复:覆盖错误的API地址
      'param_api_base_url': 'https://api-v2.example.com',
      // 修复:调整超时时间
      'param_timeout_ms': 30000,
      // 修复:切换到备用数据源
      'feature_use_backup_source': true
    };
  }
}

// ============ 在业务代码中使用热修复 ============

@Entry
@Component
struct HotFixAwarePage {
  @State apiBaseUrl: string = 'https://api.example.com';
  @State isAdvancedEditEnabled: boolean = true;
  @State useBackupSource: boolean = false;

  private hotFixManager: HotFixManager = HotFixManager.getInstance();

  aboutToAppear() {
    // 应用热修复配置
    this.applyHotFixConfig();
  }

  /**
   * 应用热修复配置
   */
  private applyHotFixConfig() {
    // 修复API地址
    this.apiBaseUrl = this.hotFixManager.getFixedParam('api_base_url', this.apiBaseUrl);

    // 修复功能开关
    this.isAdvancedEditEnabled = this.hotFixManager.isFeatureEnabled('advanced_edit');

    // 修复数据源切换
    this.useBackupSource = this.hotFixManager.isFeatureEnabled('use_backup_source');

    hilog.info(0x0001, TAG, `热修复配置已应用: api=${this.apiBaseUrl}`);
  }

  build() {
    Column() {
      Text('热修复演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 24 })

      // 显示当前修复配置
      Column({ space: 12 }) {
        this.ConfigItem('API地址', this.apiBaseUrl)
        this.ConfigItem('高级编辑', this.isAdvancedEditEnabled ? '已启用' : '已禁用(热修复)')
        this.ConfigItem('数据源', this.useBackupSource ? '备用源(热修复)' : '主数据源')
      }
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }

  @Builder
  ConfigItem(label: string, value: string) {
    Row() {
      Text(label)
        .fontSize(14)
        .fontColor('#999999')
        .width(100)
      Text(value)
        .fontSize(14)
        .fontColor('#333333')
        .layoutWeight(1)
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }
}

import { hilog } from '@kit.PerformanceAnalysisKit';

四、踩坑与注意事项

4.1 增量更新的版本碎片问题

增量更新最大的坑是版本碎片。如果你的差分包是基于 v1.0 生成的,但用户已经更新到了 v1.1,那这个差分包就没法用了。

解决方案:为每个活跃版本都生成差分包。

v2.0 差分包:
  - v1.0 → v2.0 的差分包 (patch_v1_to_v2.patch)
  - v1.1 → v2.0 的差分包 (patch_v1.1_to_v2.patch)
  - v1.2 → v2.0 的差分包 (patch_v1.2_to_v2.patch)

服务器根据客户端的当前版本号返回对应的差分包。如果找不到匹配的差分包,退化为全量更新。

4.2 强制更新的用户体验

强制更新是一把双刃剑。用好了,能确保所有用户快速升级到安全版本;用不好,用户会觉得你在"绑架"他们。

最佳实践:

  1. 强制更新只用于修复严重安全漏洞或合规问题
  2. 给用户一个宽限期(如 7 天),宽限期内提示但不强制
  3. 宽限期后,每次启动都弹出不可关闭的更新弹窗
  4. 提供更新包大小和 WiFi 下载提示

4.3 静默更新的时机选择

静默更新在后台下载更新包,下次启动时生效。但下载时机很重要:

// ✅ 好的时机:WiFi + 充电 + 空闲
if (isWiFi && isCharging && isIdle) {
  startSilentDownload();
}

// ❌ 坏的时机:移动数据 + 低电量
// 用户会投诉你偷跑流量、耗电

4.4 更新包校验

下载完成后必须校验更新包的完整性,防止中间人攻击或下载损坏:

// 校验MD5(示意代码)
async verifyPackage(filePath: string, expectedMd5: string): Promise<boolean> {
  // 计算下载文件的MD5
  const actualMd5 = await this.calculateMd5(filePath);
  if (actualMd5 !== expectedMd5) {
    hilog.error(0x0001, TAG, `MD5校验失败: 期望${expectedMd5}, 实际${actualMd5}`);
    return false;
  }
  return true;
}

4.5 热修复的局限性

HarmonyOS 的热修复方案(基于配置下发)有明显的局限性:

修复类型 是否支持 说明
功能开关 ✅ 通过配置禁用有Bug的功能
参数覆盖 ✅ 通过配置修正错误的参数
逻辑分支 ⚠️ 有限 需要预先埋好分支逻辑
ArkTS代码修复 ❌ 无法动态修改已编译的代码
UI布局修复 ❌ 无法动态修改布局文件
资源文件修复 ❌ 无法动态替换资源

核心认知:热修复不是万能的,它只是 OTA 更新的补充。真正的代码修复还是需要走 OTA 更新流程。


五、HarmonyOS 6 适配

5.1 更新框架增强

HarmonyOS 6 对 @ohos.update 进行了重大升级:

新增能力 说明
getUpdaterForModule 支持模块级更新(只更新某个 HAP 模块)
verifyUpdatePackage 更新包签名校验接口
pauseDownload / resumeDownload 下载暂停/恢复接口
getUpdateStrategy 获取服务器下发的更新策略

5.2 模块化更新

HarmonyOS 6 支持模块级更新,不需要下载整个应用包:

// 只更新某个模块
const moduleUpdater = update.getOnlineUpdater({
  vendor: 'com.example.app',
  moduleName: 'feature_module'  // 只更新这个模块
});

await moduleUpdater.download(updateInfo);
await moduleUpdater.upgrade();

5.3 安全更新链

HarmonyOS 6 强化了更新安全链:

  1. 签名校验:更新包必须经过开发者签名
  2. 完整性校验:下载后自动校验 SHA256
  3. 来源校验:只接受来自可信源的更新包
  4. 回滚机制:安装失败自动回滚到旧版本

六、总结

mindmap
  root((应用更新))
    OTA全量更新
      下载完整HAP包
      替换安装
      需要重启
      无版本限制
    OTA增量更新
      下载差分包
      本地合成新包
      需要重启
      基准版本必须匹配
      版本碎片问题
    热修复
      配置下发
        功能开关
        参数覆盖
        逻辑分支
      局限性
        不能修改代码
        不能修改UI
        需要预先埋点
    更新策略
      静默更新
        WiFi+充电+空闲
        下次启动生效
      提示更新
        弹窗提示用户
        用户可选择拒绝
      强制更新
        不可关闭弹窗
        仅用于安全修复
        设置宽限期
    注意事项
      增量版本碎片
      强制更新体验
      静默更新时机
      更新包校验
      热修复局限性
    HarmonyOS 6
      模块级更新
      下载暂停/恢复
      签名校验增强
      安装失败回滚

核心知识点回顾:

  1. 三种更新方式:全量更新(最可靠)、增量更新(最省流量)、热修复(最快但最受限)
  2. 增量更新关键:基准版本必须匹配,服务器需为每个活跃版本生成差分包
  3. 更新策略三级:静默更新(WiFi+充电)、提示更新(用户选择)、强制更新(安全修复)
  4. 热修复本质:基于配置下发的逻辑修复,不能修改代码和UI,需要预先埋点
  5. 安全校验:下载后必须校验 MD5/SHA256,防止中间人攻击
  6. HarmonyOS 6 新增:模块级更新、下载暂停/恢复、安装失败自动回滚

应用更新是连接开发者和用户的桥梁。一个好的更新体验,应该是"无感知"的——用户不需要关心版本号,不需要手动操作,一切都在后台默默完成。而一个糟糕的更新体验,会让用户觉得每次更新都是一次"冒险"。选择合适的更新策略,做好每一个细节,让更新成为提升体验的手段,而不是破坏体验的元凶。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。