《HarmonyOS技术精讲-Basic Services Kit》升级服务:应用内更新检测与下载安装

《HarmonyOS技术精讲-Basic Services Kit》升级服务:应用内更新检测与下载安装

这个问题在开发里比较常见

HarmonyOS NEXT 开发里,升级服务 API 的调用频率其实很高,但很多人第一次接触时会发现:官方示例能跑通,放到实际页面里就不太对劲。

有两个典型问题:

  • 下载进度回调一直不走
  • 安装弹窗没出来,应用就直接重启了

如果你遇到的是这类问题,说明对升级服务的生命周期和状态同步理解还不够。这篇文章会从 API 层面拆清楚,最后给出一个可直接落地的完整实现。

它解决什么问题

升级服务(@kit.BasicServicesKitupdateService模块)解决的是:应用内检测新版本 -> 下载升级包 -> 安装新版本 这一整套流程。

适用场景

场景 推荐方式
静默升级(用户无感知) 配合 DownloadTask 后台下载,然后再触发静默安装
用户触发更新 弹 Dialog 提示用户,用户确认后开始下载并引导安装
强制更新 下载完成后直接调用安装,不允许取消

不适用场景

  • 跨版本大升级(比如 API 版本差异太大)
  • 应用未登录状态下无法访问后台接口的情况

HarmonyOS 的限制是:downloadPackage 要求应用必须在 前台存活状态 下才能检查到下载进度回调。这一点很多人踩过坑。

环境说明

text 复制代码
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:搭载 HarmonyOS NEXT 的手机

核心实现

1. 权限和模块引入

升级服务不需要额外权限,但需要在 module.json5 中声明 ohos.permission.INTERNET

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

然后在代码中引入:

typescript 复制代码
import { updateService } from '@kit.BasicServicesKit';

2. 检查新版本

先判断当前应用版本和服务器返回的版本是否一致。这里放一段 VersionManager 的完整实现:

typescript 复制代码
// utils/VersionManager.ets
import { updateService } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class VersionManager {
  private static instance: VersionManager | null = null;
  private appUpdateInfo: updateService.AppUpdateInfo | null = null;

  public static getInstance(): VersionManager {
    if (VersionManager.instance === null) {
      VersionManager.instance = new VersionManager();
    }
    return VersionManager.instance;
  }

  // 检查新版本
  async checkUpdate(): Promise<updateService.AppUpdateInfo | null> {
    try {
      const result: updateService.CheckNewVersionResult = 
        await updateService.checkNewVersion();
      if (result && result.appUpdateInfo) {
        this.appUpdateInfo = result.appUpdateInfo;
        return result.appUpdateInfo;
      }
      return null;
    } catch (error) {
      console.error('检查更新失败:', (error as BusinessError).message);
      return null;
    }
  }

  // 获取当前本地版本号
  getLocalVersionName(): string {
    // 实际项目从 bundleInfo 获取
    return '1.0.0';
  }

  // 版本比较逻辑
  hasNewVersion(remoteVersion: string): boolean {
    const local = this.getLocalVersionName();
    if (!remoteVersion) return false;
    // 简单比较:字符串大小比较,实际推荐用 semver 库
    return remoteVersion.localeCompare(local) > 0;
  }
}

这段代码做了几件事:

  1. 单例模式 :避免重复新建实例,因为 AppUpdateInfo 的状态可能会被覆盖
  2. checkNewVersion :返回 AppUpdateInfo,里面包含最新版本的 versionNamedownloadUrlsize 等信息
  3. 版本比较 :这里用了字符串比较,实际项目中更推荐用 semver 库或者自己实现数字分段比较

注意事项checkNewVersion 必须在 UI 主线程 调用,而且不能频繁调用。官方文档没有明确说明限制频率,但实际测试中,连续调用超过 3 次/秒会触发节流。

3. 下载升级包

检查到有新版本后,用 downloadPackage 开始下载。下载过程中需要监听进度,这里用一个 DownloadManager 来封装:

typescript 复制代码
// utils/DownloadManager.ets
import { updateService } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class DownloadManager {
  private downloadTask: updateService.DownloadTask | null = null;

  // 开始下载
  async startDownload(downloadUrl: string): Promise<void> {
    try {
      this.downloadTask = await updateService.downloadPackage({
        url: downloadUrl,
        title: '应用更新',
        description: '正在下载最新版本...'
      });

      // 监听下载进度
      this.downloadTask.on('progress', (progress: updateService.Progress) => {
        console.log(`下载进度: ${progress.percentage}%`);
        // 这里可以发送事件通知 UI 更新
      });

      this.downloadTask.on('complete', () => {
        console.log('下载完成');
        this.installPackage();
      });

      this.downloadTask.on('fail', (error: BusinessError) => {
        console.error('下载失败:', error.message);
      });

    } catch (error) {
      console.error('启动下载失败:', (error as BusinessError).message);
    }
  }

  // 安装下载的包
  private async installPackage(): Promise<void> {
    try {
      const installTask = await updateService.install();
      installTask.on('installProgress', (progress: updateService.InstallProgress) => {
        console.log(`安装进度: ${progress.percentage}%`);
      });
      installTask.on('installFinish', () => {
        console.log('安装完成,应用即将重启');
      });
    } catch (error) {
      console.error('安装失败:', (error as BusinessError).message);
    }
  }

  // 取消下载
  cancelDownload(): void {
    if (this.downloadTask) {
      this.downloadTask.cancel();
      this.downloadTask = null;
    }
  }
}

关键点

  • downloadPackage 返回一个 DownloadTask 对象,必须在它上面注册 三个事件progresscompletefail
  • complete 事件之后直接调用 install(),这个 install() 是静默安装,不会弹用户确认
  • 如果想要用户确认安装(比如强制更新场景),需要配合系统弹窗,但 install() 本身不支持传递参数

4. 完整调用流程

在页面中整合以上两个 Manager:

typescript 复制代码
// pages/Index.ets
@Entry
@Component
struct Index {
  @State message: string = '检查更新中...';
  private versionManager: VersionManager = VersionManager.getInstance();
  private downloadManager: DownloadManager = new DownloadManager();

  async aboutToAppear() {
    await this.checkAndUpdate();
  }

  async checkAndUpdate() {
    const updateInfo = await this.versionManager.checkUpdate();
    if (!updateInfo) {
      this.message = '已是最新版本';
      return;
    }

    const hasNew = this.versionManager.hasNewVersion(updateInfo.versionName);
    if (!hasNew) {
      this.message = '已是最新版本';
      return;
    }

    this.message = `发现新版本: ${updateInfo.versionName}`;

    // 这里可以弹 Dialog 让用户选择,此处简化直接开始下载
    await this.downloadManager.startDownload(updateInfo.downloadUrl);
    this.message = '下载中...';
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
        .margin(20)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

常见问题与踩坑记录

坑1:下载进度回调不走

现象 :调用了 downloadPackageprogress 事件一直不触发,直到下载完成突然跳到 100%。

原因 :这是一个 HarmonyOS 的 回调机制限制DownloadTask 的进度回调依赖 UI 主线程的帧循环,如果页面在下载过程中进入后台,或者页面被销毁,回调就会丢失。

解决方案 :不要在页面级组件里直接监听进度,而是用一个 全局的 ServiceAbility 或者 单例对象 来持有 DownloadTask,保证即使页面被销毁,回调仍然能执行。

typescript 复制代码
// 推荐做法:在 EntryAbility 或 AppScope 里初始化 DownloadManager
// 不要在某个页面里 new 新的实例

坑2:静默安装失败

现象 :下载完成后调用 install(),应用没有重启,也没有任何反馈。

原因install() 依赖应用的状态。如果下载完成后应用被系统回收,或者网络切换导致 task 失效,安装就会失败。另外,install() 的返回值是一个 InstallTask,如果不监听 installFinish 事件,根本不知道安装结果。

解决方案 :下载完成后先 检查 task 状态,确认下载包完整后再调用安装。

typescript 复制代码
// 在 complete 回调里
this.downloadTask?.getStatus().then((status: updateService.DownloadStatus) => {
  if (status === updateService.DownloadStatus.SUCCESS) {
    this.installPackage();
  }
});

坑3:版本号格式导致比较错误

现象1.2.101.2.9 比较,字符串 "1.2.10""1.2.9" 比较结果是 "1.2.10" < "1.2.9"(因为字符 '1' > '9' 的比较逻辑只针对相同位数)。

原因:字符串排序是按字典序,不是语义化版本比较。

解决方案 :自己实现一个数字分段比较函数,或者引入 semver 库(@ohos/semver)。

typescript 复制代码
function compareVersion(v1: string, v2: string): number {
  const parts1 = v1.split('.').map(Number);
  const parts2 = v2.split('.').map(Number);
  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
    const n1 = parts1[i] || 0;
    const n2 = parts2[i] || 0;
    if (n1 !== n2) return n1 - n2;
  }
  return 0;
}

最佳实践

  1. 不要把 DownloadTask 放在页面局部变量里

    原因:页面销毁后回调就丢失了。统一用全局单例或 Ability 级别的变量管理下载状态。

  2. 下载前先检查网络状态

    原因:downloadPackage 不会自动判断网络类型。建议先用 @ohos.net.connection 检查是否是 Wi-Fi,避免用户使用移动网络下载大包。

  3. 安装完成后不要立即做 UI 更新

    原因:应用会在几秒后重启,此时 UI 更新没有意义,反而可能触发 undefined 错误。安装成功回调里只需要记录日志即可。

FAQ

Q:为什么真机正常,模拟器不生效?

A:模拟器不支持 updateService 的安装逻辑,因为模拟器没有真实的系统分区来执行安装操作。升级服务相关功能必须在真机上验证。

Q:为什么页面返回后状态丢失?

A:如果你把 DownloadTask 存在组件内,返回时组件销毁,task 也跟着销毁。解决方案见坑1。

Q:为什么第一次授权成功,第二次失败?

A:升级服务的 install() 不需要用户授权,但如果应用被系统回收后再启动,之前下载的包可能已经被清理。建议在 onCreateaboutToAppear 中重新检查是否有缓存的新版本包。

Q:可以同时下载多个更新包吗?

A:downloadPackage 允许多个实例,但不建议。同时下载多个包不仅浪费流量,而且系统会对后台下载任务做限制。建议单队列串行下载。


如果你也遇到类似问题,可以重点检查 DownloadTask 的生命周期管理和版本号比较逻辑。官方文档对升级服务的描述比较简单,实际项目里需要对异常情况做更细致的处理。不同设备上的行为可能存在差异(比如安装速度、进度回调频率),建议都试试真机测试。

相关推荐
解局易否结局8 小时前
ArkUI MVI 架构实战:单向数据流设计模式在 HarmonyOS NEXT 中的深度实践
华为·设计模式·架构·harmonyos
信仰8748 小时前
HCIA-华为数通基础理论与实践10
网络·华为·智能路由器
TechEdu2026068 小时前
HarmonyOS鸿蒙操作系统:架构、分布式能力与生态概览
操作系统·harmonyos·计算机科学
信仰8748 小时前
HCIA-华为数通基础理论与实践06
网络·华为
2301_768103499 小时前
HarmonyOS趣味相机实战第17篇:BackupExtensionAbility备份恢复与Preferences版本迁移
harmonyos·arkts·数据迁移·preferences·corefilekit
千逐6810 小时前
HarmonyOS 实战 | 数据存本地——Preferences 轻量存储入门
华为·harmonyos·鸿蒙
youtootech10 小时前
HarmonyOS《柚兔学伴》项目实战08-关系型数据库入门——待办事项持久化
jvm·数据库·华为·harmonyos
解局易否结局10 小时前
ArkTS 设计模式与架构进阶:构建可维护的 HarmonyOS 应用
华为·设计模式·架构·harmonyos
解局易否结局10 小时前
鸿蒙原生开发实战|Native JSON 编解码与高性能数据序列化
华为·json·harmonyos