鸿蒙开发 OTA升级自动安装更新包

本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、OTA升级安装流程

二、安装逻辑实现

1. 请求安装权限

鸿蒙应用需要获取ohos.permission.INSTALL_BUNDLE权限才能安装应用

在module.json5中添加权限声明

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INSTALL_BUNDLE",
        "reason": "用于应用OTA更新"
      }
    ]
  }
}

动态请求权限

import abilityAccessCtrl from '@ohos.abilityAccessCtrl';

async function requestInstallPermission() {
  const atManager = abilityAccessCtrl.createAtManager();
  
  try {
    const grantStatus = await atManager.requestPermissionsFromUser(
      getContext(), 
      ['ohos.permission.INSTALL_BUNDLE']
    );
    
    return grantStatus.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
  } catch (err) {
    console.error('权限请求失败:', err);
    return false;
  }
}

2. 执行安装操作

使用@ohos.bundle.installer模块进行安装

import installer from '@ohos.bundle.installer';
import common from '@ohos.app.ability.common';

async function installUpdate(hapPath: string) {
  const context = getContext() as common.UIAbilityContext;
  
  try {
    // 1. 获取BundleInstaller实例
    const bundleInstaller = await installer.getBundleInstaller();
    
    // 2. 设置安装参数
    const installParam: installer.InstallParam = {
      installFlag: installer.InstallFlag.REPLACE_EXISTING,
      userId: 100 // 当前用户ID
    };
    
    // 3. 执行安装
    await new Promise<void>((resolve, reject) => {
      bundleInstaller.install(hapPath, installParam, (err, data) => {
        if (err) {
          console.error('安装失败:', err.code, err.message);
          reject(err);
        } else {
          console.log('安装成功');
          resolve();
        }
      });
    });
    
    return true;
  } catch (error) {
    console.error('安装异常:', error);
    return false;
  }
}

 3. 完整的OTA安装流程实现

import fs from '@ohos.file.fs';
import hash from '@ohos.util.hash';
import request from '@ohos.request';

class OtaManager {
  private updateUrl = 'https://your-update-server.com/latest.hap';
  private hapPath: string = '';
  
  // 完整的OTA流程
  async performOTAUpdate() {
    try {
      // 1. 请求安装权限
      const hasPermission = await this.requestInstallPermission();
      if (!hasPermission) {
        this.showToast('请授权安装权限');
        return;
      }
      
      // 2. 下载更新包
      this.showProgressDialog('下载中...');
      const downloadSuccess = await this.downloadUpdate();
      this.dismissProgressDialog();
      
      if (!downloadSuccess) {
        this.showAlert('下载更新失败');
        return;
      }
      
      // 3. 验证更新包
      const isValid = await this.validateHap();
      if (!isValid) {
        this.showAlert('更新包校验失败');
        return;
      }
      
      // 4. 请求用户确认安装
      this.showInstallConfirmation();
    } catch (error) {
      console.error('OTA流程异常:', error);
      this.showAlert('更新过程中发生错误');
    }
  }
  
  // 下载更新包
  private async downloadUpdate(): Promise<boolean> {
    const context = getContext();
    this.hapPath = context.filesDir + '/update.hap';
    
    const downloadTask = request.downloadFile(context, {
      url: this.updateUrl,
      filePath: this.hapPath
    });
    
    return new Promise((resolve) => {
      downloadTask.on('progress', (receivedSize: number, totalSize: number) => {
        const percent = Math.floor((receivedSize / totalSize) * 100);
        this.updateProgress(`下载中 ${percent}%`);
      });
      
      downloadTask.on('complete', () => {
        console.log('下载完成');
        resolve(true);
      });
      
      downloadTask.on('fail', () => {
        console.error('下载失败');
        resolve(false);
      });
    });
  }
  
  // 验证HAP包完整性和签名
  private async validateHap(): Promise<boolean> {
    try {
      // 1. 检查文件是否存在
      if (!fs.accessSync(this.hapPath)) {
        console.error('HAP文件不存在');
        return false;
      }
      
      // 2. 验证文件哈希(示例使用SHA256)
      const fileHash = await this.calculateFileHash(this.hapPath);
      const expectedHash = await this.getExpectedHash(); // 从服务器获取预期哈希
      
      if (fileHash !== expectedHash) {
        console.error('文件哈希不匹配');
        return false;
      }
      
      // 3. 验证签名(伪代码,实际需要更复杂的实现)
      /*
      const signatureValid = await this.verifySignature(this.hapPath);
      if (!signatureValid) {
        return false;
      }
      */
      
      return true;
    } catch (error) {
      console.error('验证失败:', error);
      return false;
    }
  }
  
  // 计算文件哈希
  private async calculateFileHash(filePath: string): Promise<string> {
    const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    const hashObj = new hash.Hash(hash.Hash.SHA256);
    const buffer = new ArrayBuffer(4096);
    
    let bytesRead;
    while ((bytesRead = fs.readSync(file.fd, buffer)) > 0) {
      const chunk = new Uint8Array(buffer.slice(0, bytesRead));
      hashObj.update(chunk);
    }
    
    fs.closeSync(file);
    return hashObj.digest().toUpperCase();
  }
  
  // 显示安装确认对话框
  private showInstallConfirmation() {
    AlertDialog.show({
      title: '安装更新',
      message: '下载完成,是否立即安装新版本?',
      buttons: [
        {
          text: '取消',
          color: '#667788'
        },
        {
          text: '安装',
          color: '#FF0000',
          onClick: async () => {
            this.showProgressDialog('安装中...');
            const success = await this.installUpdate(this.hapPath);
            this.dismissProgressDialog();
            
            if (success) {
              this.showAlert('安装成功,请重启应用');
            } else {
              this.showAlert('安装失败');
            }
          }
        }
      ]
    });
  }
}

三、注意事项

1. 安全验证要点

  1. 完整性检查:下载后验证文件的哈希值
  2. 签名验证:确保更新包来自可信来源
  3. 权限控制:仅允许授权用户执行安装

2. 安装参数选项

参数选项说明
installFlagREPLACE_EXISTING替换现有应用
FREE_INSTALL自由安装(无签名验证)
RETAIN_DATA保留应用数据
userId100当前用户
0所有用户

3. 错误处理

// 安装错误处理示例
bundleInstaller.install(hapPath, installParam, (err) => {
  if (err) {
    switch (err.code) {
      case 201: 
        console.error('安装包不存在');
        break;
      case 202:
        console.error('安装包验证失败');
        break;
      case 203:
        console.error('签名不一致');
        break;
      case 204:
        console.error('版本不兼容');
        break;
      default:
        console.error('未知错误', err.code);
    }
  }
});

四、企业级实现

1. 差分更新优化

// 使用差分更新减小下载体积
async function applyPatchUpdate() {
  const oldFile = context.filesDir + '/current.apk';
  const patchFile = context.filesDir + '/patch.diff';
  const newFile = context.filesDir + '/new.apk';
  
  // 下载增量包
  await downloadPatchFile(patchFile);
  
  // 应用增量更新
  if (bsdiff.applyPatch(oldFile, newFile, patchFile)) {
    return newFile;
  } else {
    // 增量更新失败,回退到完整包下载
    return downloadFullPackage();
  }
}

2. 安装后验证

// 安装后验证应用完整性
async function verifyAfterInstall(bundleName: string) {
  const bundleManager = bundle.getBundleManager();
  const bundleInfo = await bundleManager.getBundleInfo(bundleName, 0);
  
  const installedVersion = bundleInfo.versionName;
  const expectedVersion = this.getLatestVersion();
  
  if (installedVersion !== expectedVersion) {
    console.error('版本不匹配,可能安装失败');
    return false;
  }
  
  const packageInfo = bundleInfo.packages[0];
  const signatureValid = verifySignature(packageInfo.signature);
  
  return signatureValid;
}

3. 安装失败恢复策略

// 安装失败处理流程
async function safeInstall(hapPath: string) {
  try {
    // 创建系统备份
    await backupApplicationData();
    
    // 尝试安装
    const result = await installUpdate(hapPath);
    
    if (!result) {
      throw new Error('安装失败');
    }
    
    // 验证安装
    if (!verifyAfterInstall()) {
      // 回滚到备份
      await restoreFromBackup();
      return false;
    }
    
    return true;
  } catch (error) {
    console.error('安装失败:', error);
    await restoreFromBackup();
    return false;
  }
}

五、总结

  1. 权限是前提:必须获取ohos.permission.INSTALL_BUNDLE权限
  2. 验证是关键:安装前必须校验文件完整性和签名
  3. 用户确认不可少:需要明确的用户确认步骤
  4. 错误处理要完善:处理常见的安装失败场景
  5. 安全始终第一:遵循最小权限原则和安全最佳实践

实际开发中,建议使用官方提供的@ohos.update接口(如果设备支持),对于无法使用系统更新的场景,再考虑使用上述的BundleInstaller方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值