HarmonyOS开发—— uni-app 鸿蒙端(HarmonyOS NEXT)整包更新适配:告别热更新,拥抱应用市场

前言

在 uni-app 跨端开发中,Android/iOS 端的热更新(WGT)已经是非常成熟的方案——通过 plus.runtime.install() 下载差量包静默安装,用户无感知即可完成版本迭代。然而,当我们把目光转向 HarmonyOS NEXT 时,问题出现了:

鸿蒙端不支持 WGT 热更新。

这不是 bug,而是平台设计使然。HarmonyOS NEXT 的应用分发体系要求通过华为应用市场(AppGallery)进行整包更新。本文将分享我们在实际项目中的适配方案:通过原生桥接调用鸿蒙 updateManager API,由官方弹窗引导用户前往应用市场更新,同时确保 Android/iOS 的热更新流程零改动。

一、问题背景

我们的项目是一个基于 uni-app + Vue3 的多端应用,同时覆盖 Android、iOS 和 HarmonyOS NEXT 三端。原有的更新体系如下:

平台 更新方式 核心 API
Android WGT 热更新 / APK 强制更新 plus.runtime.install()
iOS WGT 热更新 / App Store 跳转 plus.runtime.install()
HarmonyOS NEXT ❓ ❓

Android/iOS 的更新流程由后端配置中心下发 HOT_UPDATE_URL,客户端读取后根据版本号比较,自动判断是热更新(WGT)、强制更新(APK)还是应用市场跳转。

鸿蒙端面临的核心矛盾:

  1. plus.runtime 在鸿蒙端不可用,无法获取 WGT 版本信息
  2. 鸿蒙不支持 WGT 差量包安装
  3. 必须走华为应用市场的整包更新通道

二、方案设计

2.1 设计原则

  • 平台隔离:鸿蒙端的更新逻辑完全独立,不干扰 Android/iOS 已有流程
  • 原生能力优先:版本检测和更新弹窗均由鸿蒙原生 API 完成,前端只负责触发
  • 零侵入:Android/iOS 的更新代码不做任何改动

2.2 整体架构

┌─────────────────────────────────────────────────┐
│                 App.vue (onLaunch)               │
│           checkBundleUpdate()                    │
├─────────────────────────────────────────────────┤
│                                                  │
│   #ifdef APP-HARMONY          #ifndef APP-HARMONY│
│   ┌─────────────────┐        ┌──────────────────┐│
│   │ 鸿蒙应用市场更新  │        │ 读取后端配置      ││
│   │ checkHarmony-   │        │ HOT_UPDATE_URL   ││
│   │ MarketUpdate()  │        │ 判断 WGT/APK/    ││
│   │        ↓        │        │ Market 更新类型   ││
│   │ 原生桥接调用      │        │ 执行对应更新流程   ││
│   │ uni.checkHarmony│        └──────────────────┘│
│   │ AppUpdate()     │                            │
│   └────────┬────────┘                            │
└────────────┼─────────────────────────────────────┘
             ↓
┌─────────────────────────────────────────────────┐
│         EntryAbility.ets (原生层)                 │
│                                                  │
│  updateManager.checkAppUpdate()  → 检测新版本     │
│  updateManager.showUpdateDialog() → 官方弹窗引导  │
└─────────────────────────────────────────────────┘

三、实现细节

3.1 原生桥接层(ArkTS)

在鸿蒙原生工程 EntryAbility.ets 中,通过 updateManager API 实现版本检测和官方弹窗:

import { updateManager } from '@kit.AppStoreServiceKit';

// 在 onCreate 中注册桥接方法
Reflect.set(uni, 'checkHarmonyAppUpdate', () => this.checkHarmonyAppUpdate());

/** 仅检查鸿蒙应用市场的整包版本,由官方弹窗引导更新。 */
private async checkHarmonyAppUpdate(): Promise<string> {
  if (this.appUpdateChecking) {
    return 'checking';
  }
  this.appUpdateChecking = true;
  try {
    // 第一步:检查是否有新版本
    const result = await updateManager.checkAppUpdate(this.context);
    
    if (result.updateAvailable === updateManager.UpdateAvailableCode.LATER_VERSION_NOT_EXIST) {
      return 'not_update';  // 已是最新版本
    }
    
    if (result.updateAvailable !== updateManager.UpdateAvailableCode.LATER_VERSION_EXIST) {
      throw new Error('未知的应用市场更新检测结果');
    }
    
    // 第二步:有新版,展示官方更新弹窗
    const dialogResult = await updateManager.showUpdateDialog(this.context);
    if (dialogResult === updateManager.ShowUpdateResultCode.SHOW_DIALOG_SUCCESS) {
      return 'dialog_shown';  // 弹窗已展示
    }
    
    return 'dialog_failed';  // 弹窗展示失败
  } catch (err) {
    const error = err as BusinessError;
    throw new Error(`鸿蒙应用市场更新检查失败:${error.code} ${error.message}`);
  } finally {
    this.appUpdateChecking = false;
  }
}

关键点:

  • 使用 @kit.AppStoreServiceKit 的 updateManager,这是华为官方提供的应用市场服务接口
  • checkAppUpdate() 检测应用市场是否有新版本
  • showUpdateDialog() 弹出华为官方的更新确认弹窗,用户点击后自动跳转应用市场完成更新
  • 通过 Reflect.set(uni, ...) 将方法挂载到 uni 对象,供前端 JS 层调用

3.2 前端调用层(TypeScript)

在 useUpdateInfo.ts 中,通过条件编译隔离鸿蒙的更新逻辑:

// 鸿蒙端专用:调用原生桥接,触发应用市场更新检查
// #ifdef APP-HARMONY
async function checkHarmonyMarketUpdate(isShowNotice: boolean) {
  const harmonyApi = uni as typeof uni & {
    checkHarmonyAppUpdate?: () => Promise<string>;
  };
  
  if (typeof harmonyApi.checkHarmonyAppUpdate !== 'function') {
    throw new Error('鸿蒙应用市场更新桥接未就绪,需重新编译原生工程');
  }
  
  const result = await harmonyApi.checkHarmonyAppUpdate();
  
  if (!isShowNotice) return;
  
  // 根据原生层返回的结果,给出对应的用户提示
  if (result === 'not_update') {
    uni.showToast({ title: '当前已是最新版本', icon: 'none' });
  } else if (result === 'dialog_failed') {
    uni.showToast({ title: '发现新版本,请前往华为应用市场更新', icon: 'none' });
  } else if (result === 'checking') {
    uni.showToast({ title: '正在检查更新,请稍候', icon: 'none' });
  }
  // result === 'dialog_shown' 时,官方弹窗已展示,无需额外提示
}
// #endif

3.3 更新入口:条件编译分流

在 checkBundleUpdate 主函数中,通过 #ifdef APP-HARMONY 实现平台分流:

async function checkBundleUpdate(isShowNotice = false) {
  if (isDevEnv()) return;
  
  try {
    const widgetInfo = await getCurrentWidgetInfo();
    
    // #ifdef APP-HARMONY
    // 鸿蒙整包更新由原生应用市场判断并展示官方弹窗,不使用热更新弹窗。
    isShowPopup.value = false;
    await checkHarmonyMarketUpdate(isShowNotice);
    return;  // ← 直接返回,不走 Android 的逻辑
    // #endif
    
    // #ifndef APP-HARMONY
    // Android/iOS:从后端读取热更新配置
    const res = await getConfigKey('HOT_UPDATE_URL');
    const resp = res?.configValue;
    // ... 版本号比较、WGT 下载、APK 安装等原有逻辑
    // #endif
  } catch (e) {
    console.log('error>>', e);
  }
}

3.4 版本号获取的平台差异

鸿蒙端 plus.runtime 不可用,需要用 uni.getAppBaseInfo() 替代:

function getCurrentWidgetInfo(): Promise<{ version: string }> {
  return new Promise((resolve) => {
    // #ifdef APP-HARMONY
    let version = '0.0.0';
    try {
      const appBaseInfo: any = uni.getAppBaseInfo();
      version = appBaseInfo?.appVersion || '0.0.0';
    } catch (err) {
      console.log('getAppBaseInfo error', err);
    }
    resolve({ version });
    return;
    // #endif
    
    // #ifndef APP-HARMONY
    plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
      resolve(widgetInfo as { version: string });
    });
    // #endif
  });
}

四、踩坑记录

4.1 plus.runtime 在鸿蒙端不可用

这是最常见的坑。Android/iOS 上习以为常的 plus.runtime.getProperty()、plus.runtime.install() 在鸿蒙端全部不可用。不仅不能用来安装 WGT,连获取当前版本号都做不到。

解决方案:使用 uni.getAppBaseInfo().appVersion 获取版本号。

4.2 条件编译的边界陷阱

uni-app 的条件编译中,APP-PLUS 仅覆盖 Android/iOS,而 APP 包含所有 App 端(含鸿蒙)。因此:

// 这段代码在鸿蒙端也会执行!
// #ifdef APP-PLUS || APP-HARMONY
// ...
// #endif

// 如果只想在 Android/iOS 执行:
// #ifdef APP-PLUS
// ...
// #endif

// 如果只想在鸿蒙执行:
// #ifdef APP-HARMONY
// ...
// #endif

务必确认每个条件编译块的执行范围,避免鸿蒙端误执行 Android 的热更新逻辑。

4.3 原生桥接的注册时机

Reflect.set(uni, 'checkHarmonyAppUpdate', ...) 必须在 EntryAbility 的 onCreate 中完成,确保前端调用时桥接方法已就绪。如果注册过晚,前端会抛出"桥接未就绪"的错误。

4.4 防重入保护

用户可能快速多次触发更新检查,必须加防重入锁:

private appUpdateChecking = false;

// 检查中直接返回,不重复调用原生 API
if (this.appUpdateChecking) {
  return 'checking';
}
this.appUpdateChecking = true;
try {
  // ... 检查逻辑
} finally {
  this.appUpdateChecking = false;  // 无论成功失败都释放锁
}

五、三端更新策略对比

维度 Android iOS HarmonyOS NEXT
更新方式 WGT 热更新 / APK 强制安装 WGT 热更新 / App Store 华为应用市场整包更新
版本检测 后端配置中心 HOT_UPDATE_URL 同 Android 原生 updateManager.checkAppUpdate()
更新弹窗 自定义前端弹窗 自定义前端弹窗 华为官方弹窗
用户感知 静默下载,弹窗提示安装 静默下载,弹窗提示安装 官方弹窗引导跳转应用市场
核心 API plus.runtime.install() plus.runtime.install() updateManager.showUpdateDialog()
前端参与度 高(下载、安装、进度条) 高 低(仅触发,UI 由官方接管)

六、总结

鸿蒙 NEXT 的应用生态与 Android/iOS 有本质区别——它不允许应用内热更新,而是通过华为应用市场统一管理版本分发。对于 uni-app 开发者来说,适配的关键在于:

  1. 放弃热更新思维:不要试图在鸿蒙端复刻 Android 的 WGT 方案
  2. 善用原生能力:@kit.AppStoreServiceKit 提供了完整的版本检测和更新弹窗能力
  3. 条件编译隔离:#ifdef APP-HARMONY 是平台隔离的核心武器
  4. 桥接设计要简洁:前端只负责"触发",具体的检测、弹窗、跳转全部交给原生层

这种"前端轻、原生重"的设计模式,在鸿蒙生态日趋完善的背景下,会越来越成为跨端开发的常态。


posted on 2026-09-24 09:28  码间留白  阅读(3)  评论(0)    收藏  举报

导航