【uniapp】---- 在 uniapp 实现 app 的版本检查、下载最新版本、自动安装
1. 前言
很久之前就准备自己开发一个账单 app 出来自家使用,今年的事情比较少,先使用 uniapp 进行开发,因为没有太多的体验要求,而且 uniapp 的开发也是我比较熟悉的,因此就选择了 uniapp。由于需要家人和你关联使用,因此在数据存储上边不使用自己购买服务器的话就是一个问题!当时第一个解决方案想到的就是【坚果云】,这里不是打广告,而是我之前使用【计次郎】的时候,他需要你绑定一个云空间,然后将数据推送过去,但是在对接的时候,放弃了!然后又想到了 gitee ,所以本文章采用的就是 gitee 获取最新的版本,然后和当前版本进行对比,看看是否需要更新。
2. 实现步骤
- 检查版本更新,看看是否需要进行更新操作;
- 更新提示弹框,下载并更新应用;
- 确认创建下载任务,提示下载;
- 使用 plus.runtime.install 进行安装;
- 使用 plus.runtime.restart 进行重启。
3. 检查版本
3.1 实现分析
- 获取当前版本信息:从 versionConfig 中获取应用程序的当前版本号;
- 扫描APK文件夹中的所有文件:调用 getFilesInFolder('release/apk') 异步获取APK文件夹中的所有文件,如果未找到任何文件,记录日志并结束函数执行;
- 筛选符合版本格式的APK文件:使用正则表达式 /^\d+.\d+.\d+.apk$/ 过滤出标准格式的版本文件(如 1.0.1.apk ),如果没有符合格式的文件,记录日志并结束函数执行;
- 提取并排序版本号:从每个APK文件名中提取出版本号(移除 .apk 后缀),使用自定义的排序算法,按照语义化版本号规则(主版本.次版本.修订号)进行排序;
- 确定最新版本:通过排序后取最后一个元素的方式获取最新版本号,根据最新版本号查找对应的文件对象;
- 比较版本并更新状态:调用 compareVersions 方法比较最新版本与当前版本,根据比较结果设置 hasUpdate 标志(是否有更新可用);
- 更新组件状态:将最新版本号、更新标志和最新文件信息保存到组件的响应式数据中。
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 实现分析
- 版本号解析与转换:接收两个版本号字符串参数 v1 和 v2,使用 split('.') 将版本号按点号分割成多个部分,使用 map(Number) 将每个部分转换为数字类型,便于数值比较,得到两个数字数组 v1Parts 和 v2Parts;
- 逐位比较版本号:确定最大迭代次数为两个版本号数组长度的较大值,使用 for 循环从左到右(主版本到次版本再到修订号)依次比较每个版本部分,对于长度不同的版本号,较短数组的缺少部分视为0(通过 || 0 处理);
- 返回比较结果:如果当前比较位 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 实现分析
- 显示加载提示:调用 uni.showLoading() 显示加载中的提示框,设置 mask: true 防止用户在下载期间进行其他操作;
- 创建下载任务:调用 uni.downloadFile() 创建下载任务,传入以下参数:
- url : 要下载的APK文件URL;
- header : 请求头,包含了Gitee访问令牌用于授权;
- 定义了成功和失败的回调函数。
- 处理下载成功情况:在 success 回调中,首先隐藏加载提示;重置下载进度为0;检查HTTP状态码,若为200表示下载成功:
- 显示"下载完成"的成功提示;
- 调用 installApk() 方法安装下载好的APK文件,传入临时文件路径;
- 若状态码不为200,表示下载失败,显示相应错误提示。
- 处理下载失败情况:在 fail 回调中,隐藏加载提示;重置下载进度为0;打印错误日志到控制台;显示"下载失败,请重试"的错误提示;
- 监听下载进度:由于部分字段没有返回,所以将效果屏蔽了。
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 实现分析
- 环境检查 :首先检查 plus 对象是否存在,如果不存在(表示当前环境不支持安装功能),则显示提示信息"当前环境不支持安装"并返回 false ;
- 执行安装 :使用 await plus.runtime.install() API执行APK安装,传入三个参数:filePath :APK文件的路径;配置对象:设置 force: false ,表示不强制安装。
- 安装成功处理 :当安装成功时:显示提示信息"安装成功,即将重启";使用 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. 总结
- 版本检查:通过与服务器端存储的版本号进行对比,判断是否有新版本可用;
- 下载最新版本:若有新版本,提示用户是否立即更新;若用户同意,调用下载接口获取最新APK文件;
- 自动安装:下载完成后,自动调用安装方法,无需用户手动操作;
- 错误处理:在整个过程中,若遇到任何错误(如网络问题、权限问题等),均进行相应提示和处理,确保用户体验。