uni-app 热更新

章节介绍

热更新是移动应用开发中的一项重要功能,它允许应用在不经过应用商店审核的情况下,快速更新应用内容和修复 bug。uni-app 提供了完善的热更新机制,使开发者能够为用户提供更加及时和流畅的更新体验。本章节将详细介绍 uni-app 热更新的原理、实现方法和最佳实践。

核心知识点

1. 热更新的概念

热更新(Hot Update)是指应用在运行过程中,无需重新安装即可更新应用代码和资源的技术。在 uni-app 中,热更新主要用于更新以下内容:

  • 页面代码(.vue 文件)
  • 静态资源(图片、音频、视频等)
  • 样式文件(.css 文件)
  • 配置文件(manifest.json 等)

需要注意的是,热更新不能更新原生插件和原生代码,这些内容的更新仍然需要通过应用商店发布新版本。

2. 热更新的原理

uni-app 热更新的基本原理如下:

  1. 生成更新包:开发者将需要更新的代码和资源打包成一个更新包(通常是 .wgt 文件)
  2. 上传更新包:将更新包上传到服务器,并记录版本信息
  3. 检测更新:应用启动时或定期检查是否有新版本的更新包
  4. 下载更新包:如果有新版本,下载更新包到本地
  5. 安装更新包:解压并安装更新包,替换旧的代码和资源
  6. 重启应用:重启应用以应用新的代码和资源

3. 热更新的优势

  • 快速发布:绕过应用商店审核,快速发布更新
  • 即时修复:及时修复应用中的 bug 和安全问题
  • 用户体验:无需用户手动更新,提供无缝的更新体验
  • 成本降低:减少应用商店版本发布的频率和成本
  • 灵活迭代:支持小范围灰度发布和 A/B 测试

4. 热更新的限制

  • 平台限制

    • iOS:Apple 官方限制热更新,可能导致应用被拒
    • Android:支持热更新,但部分厂商可能有额外限制
    • 小程序:不支持热更新,必须通过平台审核发布
  • 内容限制:不能更新原生插件和原生代码

  • 安全限制:需要确保热更新包的安全性,防止被篡改

5. 热更新的实现方式

uni-app 提供了两种热更新实现方式:

  • uniCloud 热更新:基于 uniCloud 服务的热更新方案,简单易用
  • 自定义热更新:开发者自行实现热更新逻辑,更加灵活可控

实用案例分析

案例一:使用 uniCloud 热更新

功能说明

使用 uniCloud 提供的热更新服务,实现应用的自动检测、下载和安装更新。

实现步骤

  1. 开通 uniCloud 服务

    在 HBuilderX 中,点击「uniCloud」->「开通服务空间」,按照提示完成开通。

  2. 创建热更新云函数

    在 uniCloud 服务空间中,创建一个名为 check-update 的云函数:

    // check-update/index.js
    'use strict';
    
    exports.main = async (event, context) => {
      // 获取客户端版本号
      const clientVersion = event.version;
      
      // 最新版本信息
      const latestVersion = {
        version: '1.0.1',

description: '\n1. 修复了登录失败的问题\n2. 优化了首页加载速度\n3. 新增了个人中心页面',
downloadUrl: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-uni-app/1.0.1/1.0.1.wgt'
};

 // 比较版本号
 if (compareVersion(clientVersion, latestVersion.version) < 0) {
   return {
     hasUpdate: true,
     version: latestVersion.version,

description: latestVersion.description,
downloadUrl: latestVersion.downloadUrl
};
} else {
return {
hasUpdate: false
};
}
};

// 版本号比较函数
function compareVersion(version1, version2) {
const arr1 = version1.split('.');
const arr2 = version2.split('.');
const length1 = arr1.length;
const length2 = arr2.length;
const minlength = Math.min(length1, length2);
let i = 0;
for (; i < minlength; i++) {
const a = parseInt(arr1[i]);
const b = parseInt(arr2[i]);
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
}
if (length1 > length2) {
for (let j = i; j < length1; j++) {
if (parseInt(arr1[j]) != 0) {
return 1;
}
}
return 0;
} else if (length1 < length2) {
for (let j = i; j < length2; j++) {
if (parseInt(arr2[j]) != 0) {
return -1;
}
}
return 0;
}
return 0;
}


3. **制作更新包**

在 HBuilderX 中,选择「发行」->「原生 APP-制作应用wgt包」,按照提示完成更新包的制作。

4. **上传更新包**

将生成的 .wgt 文件上传到服务器或云存储,并记录下载地址。

5. **实现热更新检测**

```javascript
// app.vue
export default {
  onLaunch() {
    // 检测热更新
    this.checkUpdate();
  },
  methods: {
    async checkUpdate() {
      try {
        // 获取当前应用版本号
        const appInfo = uni.getAppInfo();
        const version = appInfo.version;
        
        // 调用云函数检查更新
        const res = await uniCloud.callFunction({
          name: 'check-update',
          data: {
            version: version
          }
        });
        
        if (res.result.hasUpdate) {
          // 显示更新提示
          uni.showModal({
            title: '发现新版本',
            content: res.result.description,
            showCancel: true,
            confirmText: '立即更新',
            cancelText: '稍后更新',
            success: (modalRes) => {
              if (modalRes.confirm) {
                // 下载更新包
                this.downloadUpdate(res.result.downloadUrl);
              }
            }
          });
        }
      } catch (error) {
        console.log('检查更新失败', error);
      }
    },
    downloadUpdate(downloadUrl) {
      // 显示下载进度
      uni.showLoading({
        title: '正在下载更新...'
      });
      
      // 下载更新包
      uni.downloadFile({
        url: downloadUrl,
        success: (downloadResult) => {
          if (downloadResult.statusCode === 200) {
            // 安装更新包
            this.installUpdate(downloadResult.tempFilePath);
          } else {
            uni.hideLoading();
            uni.showToast({
              title: '下载更新包失败',
              icon: 'none'
            });
          }
        },
        fail: (err) => {
          uni.hideLoading();
          uni.showToast({
            title: '下载更新包失败',
            icon: 'none'
          });
          console.log('下载更新包失败', err);
        }
      });
    },
    installUpdate(tempFilePath) {
      // 安装更新包
      uni.updateAppPlus({
        apkPath: tempFilePath,
        success: (res) => {
          uni.hideLoading();
          uni.showModal({
            title: '更新完成',
            content: '应用将重启以应用更新',
            showCancel: false,
            confirmText: '确定',
            success: () => {
              // 重启应用
              plus.runtime.restart();
            }
          });
        },
        fail: (err) => {
          uni.hideLoading();
          uni.showToast({
            title: '安装更新包失败',
            icon: 'none'
          });
          console.log('安装更新包失败', err);
        }
      });
    }
  }
}

案例二:实现自定义热更新

功能说明

实现一个自定义的热更新方案,包括版本检测、更新包下载、安装和回滚机制。

实现步骤

  1. 创建版本管理接口

    在服务器端创建一个版本管理接口,用于返回最新版本信息:

    // server/api/update.js
    const express = require('express');
    const router = express.Router();
    
    // 版本信息
    const versions = [
      {
        version: '1.0.0',
        minVersion: '1.0.0',

description: '初始版本',
downloadUrl: 'https://example.com/updates/1.0.0.wgt',
forceUpdate: false
},
{
version: '1.0.1',
minVersion: '1.0.0',
description: '修复了登录失败的问题',
downloadUrl: 'https://example.com/updates/1.0.1.wgt',
forceUpdate: false
},
{
version: '1.0.2',
minVersion: '1.0.0',
description: '修复了严重的安全漏洞',
downloadUrl: 'https://example.com/updates/1.0.2.wgt',
forceUpdate: true
}
];

// 检查更新接口
router.get('/check', (req, res) => {
const clientVersion = req.query.version;
const platform = req.query.platform;

 // 获取最新版本
 const latestVersion = versions[versions.length - 1];
 
 // 比较版本号
 if (compareVersion(clientVersion, latestVersion.version) < 0) {
   // 检查是否需要强制更新
   if (compareVersion(clientVersion, latestVersion.minVersion) < 0) {
     latestVersion.forceUpdate = true;
   }
   
   res.json({
     code: 0,
     data: {
       hasUpdate: true,
       version: latestVersion.version,

description: latestVersion.description,
downloadUrl: latestVersion.downloadUrl,
forceUpdate: latestVersion.forceUpdate
}
});
} else {
res.json({
code: 0,
data: {
hasUpdate: false
}
});
}
});

// 版本号比较函数
function compareVersion(version1, version2) {
const arr1 = version1.split('.');
const arr2 = version2.split('.');
const length1 = arr1.length;
const length2 = arr2.length;
const minlength = Math.min(length1, length2);
let i = 0;
for (; i < minlength; i++) {
const a = parseInt(arr1[i]);
const b = parseInt(arr2[i]);
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
}
if (length1 > length2) {
for (let j = i; j < length1; j++) {
if (parseInt(arr1[j]) != 0) {
return 1;
}
}
return 0;
} else if (length1 < length2) {
for (let j = i; j < length2; j++) {
if (parseInt(arr2[j]) != 0) {
return -1;
}
}
return 0;
}
return 0;
}

module.exports = router;


2. **实现热更新管理器**

```javascript
// utils/hotUpdateManager.js
class HotUpdateManager {
  constructor() {
    this.updateInfo = null;
    this.downloadTask = null;
  }
  
  // 检查更新
  async checkUpdate() {
    try {
      // 获取当前应用版本号
      const appInfo = uni.getAppInfo();
      const version = appInfo.version;
      
      // 获取平台信息
      const systemInfo = uni.getSystemInfoSync();
      const platform = systemInfo.platform;
      
      // 调用服务器接口检查更新
      const res = await uni.request({
        url: 'https://example.com/api/update/check',
        method: 'GET',
        data: {
          version: version,
          platform: platform
        }
      });
      
      if (res.statusCode === 200 && res.data.code === 0) {
        return res.data.data;
      } else {
        throw new Error('检查更新失败');
      }
    } catch (error) {
      console.log('检查更新失败', error);
      return { hasUpdate: false };
    }
  }
  
  // 下载更新包
  downloadUpdate(downloadUrl, progressCallback) {
    return new Promise((resolve, reject) => {
      // 显示下载进度
      uni.showLoading({
        title: '正在下载更新...'
      });
      
      // 下载更新包
      this.downloadTask = uni.downloadFile({
        url: downloadUrl,
        success: (downloadResult) => {
          uni.hideLoading();
          if (downloadResult.statusCode === 200) {
            resolve(downloadResult.tempFilePath);
          } else {
            reject(new Error('下载更新包失败'));
          }
        },
        fail: (err) => {
          uni.hideLoading();
          reject(err);
        },
        progress: (progress) => {
          if (progressCallback) {
            progressCallback(progress.progress);
          }
        }
      });
    });
  }
  
  // 安装更新包
  installUpdate(tempFilePath) {
    return new Promise((resolve, reject) => {
      // 安装更新包
      uni.updateAppPlus({
        apkPath: tempFilePath,
        success: (res) => {
          console.log('安装更新包成功', res);
          // 保存当前版本信息
          this.saveVersionInfo();
          resolve(res);
        },
        fail: (err) => {
          console.log('安装更新包失败', err);
          reject(err);
        }
      });
    });
  }
  
  // 保存版本信息
  saveVersionInfo() {
    try {
      const appInfo = uni.getAppInfo();
      uni.setStorageSync('lastVersion', appInfo.version);
      uni.setStorageSync('updateTime', new Date().getTime());
    } catch (error) {
      console.log('保存版本信息失败', error);
    }
  }
  
  // 回滚到上一个版本
  rollback() {
    try {
      // 清除本地版本信息
      uni.removeStorageSync('lastVersion');
      uni.removeStorageSync('updateTime');
      
      // 重启应用
      plus.runtime.restart();
    } catch (error) {
      console.log('回滚失败', error);
    }
  }
  
  // 取消下载
  cancelDownload() {
    if (this.downloadTask) {
      this.downloadTask.abort();
      this.downloadTask = null;
    }
  }
}

export default new HotUpdateManager();
  1. 使用热更新管理器

    // app.vue
    import hotUpdateManager from '@/utils/hotUpdateManager';
    
    export default {
      onLaunch() {
        // 检测热更新
        this.checkUpdate();
      },
      methods: {
        async checkUpdate() {
          try {
            // 检查更新
            const updateInfo = await hotUpdateManager.checkUpdate();
            
            if (updateInfo.hasUpdate) {
              // 显示更新提示
              this.showUpdateDialog(updateInfo);
            }
          } catch (error) {
            console.log('检查更新失败', error);
          }
        },
        showUpdateDialog(updateInfo) {
          uni.showModal({
            title: '发现新版本',
            content: updateInfo.description,
            showCancel: !updateInfo.forceUpdate,
            confirmText: '立即更新',
            cancelText: '稍后更新',
            success: async (modalRes) => {
              if (modalRes.confirm) {
                try {
                  // 下载更新包
                  const tempFilePath = await hotUpdateManager.downloadUpdate(
                    updateInfo.downloadUrl,
                    (progress) => {
                      console.log('下载进度', progress);
                    }
                  );
                  
                  // 安装更新包
                  await hotUpdateManager.installUpdate(tempFilePath);
                  
                  // 显示安装成功提示
                  uni.showModal({
                    title: '更新完成',
                    content: '应用将重启以应用更新',
                    showCancel: false,
                    success: () => {
                      // 重启应用
                      plus.runtime.restart();
                    }
                  });
                } catch (error) {
                  uni.showToast({
                    title: '更新失败,请稍后重试',
                    icon: 'none'
                  });
                }
              }
            }
          });
        }
      }
    }

案例三:实现灰度发布和 A/B 测试

功能说明

实现一个支持灰度发布和 A/B 测试的热更新方案,能够根据用户分组推送不同的更新内容。

实现步骤

  1. 创建灰度发布接口

    // server/api/gray-update.js
    const express = require('express');
    const router = express.Router();
    
    // 灰度规则
    const grayRules = {
      // 按用户 ID 分组
      userId: {
        groupA: ['user1', 'user2', 'user3'],
        groupB: ['user4', 'user5', 'user6']
      },
      // 按百分比分组
      percentage: {
        groupA: 30, // 30%
        groupB: 70  // 70%
      }
    };
    
    // 版本信息
    const versions = {
      groupA: {
        version: '1.0.1-A',

description: '灰度版本 A:新增了深色模式',
downloadUrl: 'https://example.com/updates/1.0.1-A.wgt',
forceUpdate: false
},
groupB: {
version: '1.0.1-B',
description: '灰度版本 B:新增了浅色模式',
downloadUrl: 'https://example.com/updates/1.0.1-B.wgt',
forceUpdate: false
}
};

// 检查更新接口
router.get('/check', (req, res) => {
const clientVersion = req.query.version;
const userId = req.query.userId;
const deviceId = req.query.deviceId;

 // 确定用户分组
 let userGroup = 'groupA';
 
 // 按用户 ID 分组
 if (grayRules.userId.groupA.includes(userId)) {
   userGroup = 'groupA';
 } else if (grayRules.userId.groupB.includes(userId)) {
   userGroup = 'groupB';
 } else {
   // 按百分比分组
   const random = Math.random() * 100;
   if (random <= grayRules.percentage.groupA) {
     userGroup = 'groupA';
   } else {
     userGroup = 'groupB';
   }
 }
 
 // 获取对应分组的版本信息
 const versionInfo = versions[userGroup];
 
 // 比较版本号
 if (compareVersion(clientVersion, versionInfo.version) < 0) {
   res.json({
     code: 0,
     data: {
       hasUpdate: true,
       version: versionInfo.version,

description: versionInfo.description,
downloadUrl: versionInfo.downloadUrl,
forceUpdate: versionInfo.forceUpdate,
userGroup: userGroup
}
});
} else {
res.json({
code: 0,
data: {
hasUpdate: false,
userGroup: userGroup
}
});
}
});

// 版本号比较函数
function compareVersion(version1, version2) {
// 实现版本号比较逻辑
// ...
}

module.exports = router;


2. **实现灰度发布客户端**

```javascript
// utils/grayUpdateManager.js
class GrayUpdateManager {
  constructor() {
    this.updateManager = require('./hotUpdateManager').default;
  }
  
  // 检查灰度更新
  async checkGrayUpdate() {
    try {
      // 获取用户信息
      const userId = uni.getStorageSync('userId') || 'anonymous';
      const deviceId = uni.getStorageSync('deviceId') || this.getDeviceId();
      
      // 获取当前应用版本号
      const appInfo = uni.getAppInfo();
      const version = appInfo.version;
      
      // 调用灰度发布接口
      const res = await uni.request({
        url: 'https://example.com/api/gray-update/check',
        method: 'GET',
        data: {
          version: version,
          userId: userId,
          deviceId: deviceId
        }
      });
      
      if (res.statusCode === 200 && res.data.code === 0) {
        // 保存用户分组信息
        if (res.data.data.userGroup) {
          uni.setStorageSync('userGroup', res.data.data.userGroup);
        }
        
        return res.data.data;
      } else {
        throw new Error('检查灰度更新失败');
      }
    } catch (error) {
      console.log('检查灰度更新失败', error);
      return { hasUpdate: false };
    }
  }
  
  // 获取设备 ID
  getDeviceId() {
    try {
      const systemInfo = uni.getSystemInfoSync();
      const deviceId = systemInfo.deviceId || systemInfo.platform + '_' + Date.now();
      uni.setStorageSync('deviceId', deviceId);
      return deviceId;
    } catch (error) {
      console.log('获取设备 ID 失败', error);
      return 'unknown_' + Date.now();
    }
  }
  
  // 执行灰度更新
  async performGrayUpdate() {
    try {
      // 检查灰度更新
      const updateInfo = await this.checkGrayUpdate();
      
      if (updateInfo.hasUpdate) {
        // 执行更新
        // ...
      }
    } catch (error) {
      console.log('执行灰度更新失败', error);
    }
  }
}

export default new GrayUpdateManager();
  1. 使用灰度发布

    // app.vue
    import grayUpdateManager from '@/utils/grayUpdateManager';
    
    export default {
      onLaunch() {
        // 执行灰度更新
        grayUpdateManager.performGrayUpdate();
      }
    };

技术要点总结

  1. 热更新的原理:了解热更新的基本原理和工作流程,包括版本检测、更新包下载和安装。

  2. 平台差异:不同平台对热更新的支持程度不同,需要:

    • iOS:注意 Apple 官方的限制,避免应用被拒
    • Android:处理不同厂商系统的差异
    • 小程序:了解平台的更新机制
  3. 安全性

    • 对更新包进行签名验证,防止被篡改
    • 使用 HTTPS 协议传输更新包
    • 对敏感操作进行权限控制
  4. 用户体验

    • 提供清晰的更新提示和进度反馈
    • 支持后台下载和静默更新
    • 实现强制更新和可选更新
  5. 稳定性

    • 实现更新失败的回滚机制
    • 对更新包进行完整性校验
    • 处理各种异常情况
  6. 版本管理

    • 建立完善的版本号管理机制
    • 支持增量更新和全量更新
    • 实现版本回滚功能

学习目标

  1. 了解 uni-app 热更新的基本原理和工作流程
  2. 掌握 uni-app 热更新的实现方法
  3. 学会使用 uniCloud 热更新服务
  4. 理解如何实现自定义热更新方案
  5. 掌握灰度发布和 A/B 测试的实现方法
  6. 了解热更新的安全性和稳定性保障措施

章节小结

本章节详细介绍了 uni-app 应用的热更新功能,包括热更新的原理、实现方法和最佳实践。通过三个实用案例,展示了如何使用 uniCloud 热更新、实现自定义热更新和灰度发布功能。

在实现热更新功能时,开发者需要注意以下几点:

  1. 了解并遵守各平台对热更新的限制和要求
  2. 确保热更新过程的安全性和稳定性
  3. 提供良好的用户体验,包括清晰的更新提示和进度反馈
  4. 实现完善的版本管理和回滚机制
  5. 定期测试热更新功能,确保其正常工作

通过合理使用热更新功能,开发者可以快速发布更新、及时修复 bug、优化用户体验,同时减少应用商店审核的等待时间和成本。然而,开发者也应该谨慎使用热更新,避免滥用导致应用被平台下架或用户体验下降。

热更新是一把双刃剑,只有在合适的场景下合理使用,才能发挥其最大的价值。

« 上一篇 uni-app 后台运行 下一篇 » uni-app 多端适配技巧