【uniapp】---- 在 uniapp 实现 app 的版本检查、下载最新版本、自动安装

【uniapp】---- 在 uniapp 实现 app 的版本检查、下载最新版本、自动安装

1. 前言

很久之前就准备自己开发一个账单 app 出来自家使用,今年的事情比较少,先使用 uniapp 进行开发,因为没有太多的体验要求,而且 uniapp 的开发也是我比较熟悉的,因此就选择了 uniapp。由于需要家人和你关联使用,因此在数据存储上边不使用自己购买服务器的话就是一个问题!当时第一个解决方案想到的就是【坚果云】,这里不是打广告,而是我之前使用【计次郎】的时候,他需要你绑定一个云空间,然后将数据推送过去,但是在对接的时候,放弃了!然后又想到了 gitee ,所以本文章采用的就是 gitee 获取最新的版本,然后和当前版本进行对比,看看是否需要更新。

2. 实现步骤

  1. 检查版本更新,看看是否需要进行更新操作;
  2. 更新提示弹框,下载并更新应用;
  3. 确认创建下载任务,提示下载;
  4. 使用 plus.runtime.install 进行安装;
  5. 使用 plus.runtime.restart 进行重启。

3. 检查版本

3.1 实现分析

  1. 获取当前版本信息:从 versionConfig 中获取应用程序的当前版本号;
  2. 扫描APK文件夹中的所有文件:调用 getFilesInFolder('release/apk') 异步获取APK文件夹中的所有文件,如果未找到任何文件,记录日志并结束函数执行;
  3. 筛选符合版本格式的APK文件:使用正则表达式 /^\d+.\d+.\d+.apk$/ 过滤出标准格式的版本文件(如 1.0.1.apk ),如果没有符合格式的文件,记录日志并结束函数执行;
  4. 提取并排序版本号:从每个APK文件名中提取出版本号(移除 .apk 后缀),使用自定义的排序算法,按照语义化版本号规则(主版本.次版本.修订号)进行排序;
  5. 确定最新版本:通过排序后取最后一个元素的方式获取最新版本号,根据最新版本号查找对应的文件对象;
  6. 比较版本并更新状态:调用 compareVersions 方法比较最新版本与当前版本,根据比较结果设置 hasUpdate 标志(是否有更新可用);
  7. 更新组件状态:将最新版本号、更新标志和最新文件信息保存到组件的响应式数据中。

3.2 实现代码

javascript 复制代码
// 检查版本更新
async checkForUpdates() {
  try {
    // 获取当前版本
    const currentVersion = versionConfig.currentVersion

    // 获取apk文件夹中的所有文件
    const files = await getFilesInFolder('release/apk')
    if (!files || files.length === 0) {
      console.log('未找到任何更新文件')
      return
    }

    // 过滤出版本号格式的apk文件(如1.0.1.apk)
    const versionFiles = files.filter(
      (file) => file.name && /^\d+\.\d+\.\d+\.apk$/.test(file.name)
    )

    if (versionFiles.length === 0) {
      console.log('未找到版本格式的更新文件')
      return
    }

    // 提取版本号并排序,找到最新版本
    const versionNumbers = versionFiles.map((file) => {
      // 从文件名中提取版本号(去掉.apk后缀)
      return file.name.replace('.apk', '')
    })

    // 按版本号排序,找到最大版本号
    const latestVersion = versionNumbers
      .sort((a, b) => {
        const aParts = a.split('.').map(Number)
        const bParts = b.split('.').map(Number)

        for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
          const aPart = aParts[i] || 0
          const bPart = bParts[i] || 0

          if (aPart > bPart) return 1
          if (aPart < bPart) return -1
        }

        return 0
      })
      .pop()

    // 查找最新版本 file.name 是 1.0.1.apk
    const latestFile = versionFiles.find((file) => file.name === `${latestVersion}.apk`)

    // 比较当前版本和最新版本
    const hasUpdate = this.compareVersions(latestVersion, currentVersion) > 0

    // 更新数据
    this.latestVersion = latestVersion
    this.hasUpdate = hasUpdate
    this.latestFile = latestFile
  } catch (error) {
    console.error('检查更新失败:', error)
  }
}

4. 获取所有版本号

javascript 复制代码
// 查询gitee一个文件夹下有哪些文件
export async function getFilesInFolder(folderPath) {
  try {
    const apiUrl = `${basePath(folderPath)}/${folderPath}?${baseParams(folderPath)}`;
    const response = await httpRequest({
      url: apiUrl,
      method: 'GET'
    });
    if (response.ok) {
      const files = await response.json();
      return files;
    } else {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error('查询Gitee文件夹下文件失败:', error);
    return false;
  }
}

5. 对比版本号

5.1 实现分析

  1. 版本号解析与转换:接收两个版本号字符串参数 v1 和 v2,使用 split('.') 将版本号按点号分割成多个部分,使用 map(Number) 将每个部分转换为数字类型,便于数值比较,得到两个数字数组 v1Parts 和 v2Parts;
  2. 逐位比较版本号:确定最大迭代次数为两个版本号数组长度的较大值,使用 for 循环从左到右(主版本到次版本再到修订号)依次比较每个版本部分,对于长度不同的版本号,较短数组的缺少部分视为0(通过 || 0 处理);
  3. 返回比较结果:如果当前比较位 v1Part > v2Part ,返回 1 (表示v1版本更新),如果当前比较位 v1Part < v2Part ,返回 -1 (表示v2版本更新),如果所有对应位都相等,返回 0 (表示两个版本相同)。

5.2 实现代码

javascript 复制代码
compareVersions(v1, v2) {
  const v1Parts = v1.split('.').map(Number)
  const v2Parts = v2.split('.').map(Number)

  for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
    const v1Part = v1Parts[i] || 0
    const v2Part = v2Parts[i] || 0

    if (v1Part > v2Part) return 1
    if (v1Part < v2Part) return -1
  }

  return 0
}

6. 更新提示

javascript 复制代码
downloadUpdate() {
  if (!this.latestFile) {
    uni.showToast({
      title: '更新文件信息不完整',
      icon: 'none'
    })
    return
  }

  // 显示确认对话框
  uni.showModal({
    title: '版本更新',
    content: `发现新版本 ${this.latestVersion},是否立即更新?`,
    showCancel: true,
    confirmText: '立即更新',
    cancelText: '稍后更新',
    success: (res) => {
      if (res.confirm) {
        // 用户点击立即更新
        this.startDownload(this.latestFile.download_url)
      }
    }
  })
}

7. 下载最新版本

7.1 实现分析

  1. 显示加载提示:调用 uni.showLoading() 显示加载中的提示框,设置 mask: true 防止用户在下载期间进行其他操作;
  2. 创建下载任务:调用 uni.downloadFile() 创建下载任务,传入以下参数:
  • url : 要下载的APK文件URL;
  • header : 请求头,包含了Gitee访问令牌用于授权;
  • 定义了成功和失败的回调函数。
  1. 处理下载成功情况:在 success 回调中,首先隐藏加载提示;重置下载进度为0;检查HTTP状态码,若为200表示下载成功:
  • 显示"下载完成"的成功提示;
  • 调用 installApk() 方法安装下载好的APK文件,传入临时文件路径;
  • 若状态码不为200,表示下载失败,显示相应错误提示。
  1. 处理下载失败情况:在 fail 回调中,隐藏加载提示;重置下载进度为0;打印错误日志到控制台;显示"下载失败,请重试"的错误提示;
  2. 监听下载进度:由于部分字段没有返回,所以将效果屏蔽了。

7.2 实现代码

javascript 复制代码
startDownload(apkUrl) {
  uni.showLoading({
    title: '加载中...',
    mask: true
  })
  // 创建下载任务
  const downloadTask = uni.downloadFile({
    url: apkUrl,
    header: {
      Authorization: 'Bearer ' + GITEE_CONFIG.accessToken
    },
    success: (downloadResult) => {
      uni.hideLoading()
      this.downloadProgress = 0 // 重置进度
      if (downloadResult.statusCode === 200) {
        uni.showToast({
          title: '下载完成',
          icon: 'success'
        })
        // 下载成功,安装APK
        this.installApk(downloadResult.tempFilePath)
      } else {
        uni.showToast({
          title: '下载失败',
          icon: 'none'
        })
      }
    },
    fail: (err) => {
      uni.hideLoading()
      this.downloadProgress = 0 // 重置进度
      console.error('下载失败', err)
      uni.showToast({
        title: '下载失败,请重试',
        icon: 'none'
      })
    }
  })

  // 监听下载进度
  downloadTask.onProgressUpdate((res) => {
    console.log('下载进度', res.progress)
    console.log('已经下载的数据长度' + res.totalBytesWritten)
    console.log('预期需要下载的数据总长度' + res.totalBytesExpectedToWrite)
    // 实现下载进度条弹窗效果
    // this.downloadProgress = res.progress
    // this.downloadedBytes = res.totalBytesWritten
    // this.totalBytes = res.totalBytesExpectedToWrite
  })
}

8. 安装APK

8.1 实现分析

  1. 环境检查 :首先检查 plus 对象是否存在,如果不存在(表示当前环境不支持安装功能),则显示提示信息"当前环境不支持安装"并返回 false ;
  2. 执行安装 :使用 await plus.runtime.install() API执行APK安装,传入三个参数:filePath :APK文件的路径;配置对象:设置 force: false ,表示不强制安装。
  3. 安装成功处理 :当安装成功时:显示提示信息"安装成功,即将重启";使用 setTimeout 在1.5秒后调用 plus.runtime.restart() 重启应用。

8.2 实现代码

javascript 复制代码
async installApk(filePath) {
  if (!plus) {
    uni.showToast({
      title: '当前环境不支持安装',
      icon: 'none'
    })
    return false
  }
  await plus.runtime.install(
    filePath,
    {
      force: false // 是否强制安装
    },
    () => {
      uni.showToast({
        title: '安装成功,即将重启',
        icon: 'none'
      })
      // 安装成功后重启应用
      setTimeout(() => {
        plus.runtime.restart()
      }, 1500)
    },
    (err) => {
      console.error('安装失败', err)
      uni.showToast({
        title: '安装失败,请重试',
        icon: 'none'
      })
    }
  )
}

9. 总结

  1. 版本检查:通过与服务器端存储的版本号进行对比,判断是否有新版本可用;
  2. 下载最新版本:若有新版本,提示用户是否立即更新;若用户同意,调用下载接口获取最新APK文件;
  3. 自动安装:下载完成后,自动调用安装方法,无需用户手动操作;
  4. 错误处理:在整个过程中,若遇到任何错误(如网络问题、权限问题等),均进行相应提示和处理,确保用户体验。
相关推荐
立方世界3 小时前
HTML编写规则及性能优化深度解析:从基础到企业级实践
前端·性能优化·html
JarvanMo3 小时前
请停止用 Java 习惯来破坏你的 Flutter 代码
前端
超级神性造梦机器3 小时前
当“提示词工程”过时,谁来帮开发者管好 AI 的“注意力”?
前端·后端
被巨款砸中3 小时前
Jessibuca 播放器
前端·javascript·vue.js·web
吃饺子不吃馅3 小时前
小明问:要不要加入创业公司?
前端·面试·github
不渡_3 小时前
Web项目-版本号
前端·javascript
Asort3 小时前
JavaScript设计模式(十一):享元模式(Flyweight) - 优化内存与性能的利器
前端·javascript·设计模式
Asort3 小时前
JavaScript设计模式(十)——外观模式 (Facade)
前端·javascript·设计模式
创码小奇客3 小时前
前端小白从零到一:架构师视角下的学习路线与实战指南
前端·javascript·架构