在移动应用开发中,确保用户始终使用最新版本是提升体验、修复漏洞和推送新功能的关键。对于使用 UniApp 开发的原生 App(Android/iOS),我们无法依赖应用商店的审核流程来实现即时更新。因此,一套稳定可靠的应用内更新机制就显得尤为重要。
本文将结合 App.vue 中的核心代码,详细拆解 UniApp 如何利用其内置的 plus API 实现从版本检测 到APK 下载安装 的完整流程,并支持强制更新 与可选更新两种模式。
一、核心代码展示 (App.vue)
首先,让我们聚焦于 App.vue 中负责更新逻辑的关键部分:
// App.vue
<script>
import { getLastestVersion } from "@/static/api/api.js"
export default {
onLaunch: function () {
console.log('App Launch')
this.checkAppUpdate() // 应用启动时检查更新
},
// ... 其他生命周期函数
methods: {
// 1. 检查应用更新
checkAppUpdate() {
// 开发环境跳过检查
const isDev = process.env.NODE_ENV === 'development'
if (isDev) {
return console.log('开发环境,不执行更新检查')
}
// 获取当前应用信息
plus.runtime.getProperty(plus.runtime.appid, (info) => {
const currentVersion = info.version // 当前本地版本号
// 调用 API 获取服务器上的最新版本信息
getLastestVersion().then(res => {
if (!res.data) { return }
const { version, isforce, appFileList } = res?.data || {}
// 比较版本号
if (currentVersion != version) {
// 非强制更新:弹出确认框
if (isforce === '0') {
uni.showModal({
title: '更新提示',
content: '发现新版本,是否立即更新?',
success: (modalRes) => {
if (modalRes.confirm) {
// 用户确认后开始下载
this.downloadAndInstallApk(appFileList[0].filePath)
}
},
})
}
// 强制更新:直接下载安装
else {
this.downloadAndInstallApk(appFileList[0].filePath)
}
}
})
})
},
// 2. 下载并安装 APK
downloadAndInstallApk(url) {
uni.showLoading({ title: '下载新版本...' })
// 创建下载任务
const downloadTask = plus.downloader.createDownload(
url,
{ filename: '_doc/update/' }, // 指定下载路径
(downloadedFile, status) => { // 下载完成回调
uni.hideLoading()
if (status === 200) {
// 安装 APK 文件
plus.runtime.install(downloadedFile.filename, { force: true }, () => {
// 安装成功后的回调
// 注意:此处注释掉了重启,避免循环调用 onLaunch
// plus.runtime.restart()
}, (e) => {
// 安装失败
uni.showToast({ title: '安装失败: ' + e.message, icon: 'none' })
})
} else {
// 下载失败
uni.showToast({ title: '下载失败', icon: 'none' })
}
}
)
downloadTask.start() // 启动下载
},
}
}
</script>
二、代码逻辑深度剖析
整个更新流程可以清晰地分为两个阶段:版本检测 和 下载安装。
阶段一:版本检测 (checkAppUpdate)
- 环境判断 : 首先通过
process.env.NODE_ENV判断是否为开发环境。在 HBuilderX 中调试时,此逻辑会阻止不必要的网络请求和更新弹窗,提升开发效率。 - 获取本地版本号 : 使用
plus.runtime.getProperty这个 5+ Runtime API 来获取当前 App 的元数据。其中info.version就是我们manifest.json中配置的应用版本号。 - 请求云端最新版本 : 调用自定义的
getLastestVersionAPI 接口,向自己的服务器请求最新的版本信息。返回的数据结构通常包含:version: 最新版本号(用于与本地比较)。isforce: 是否强制更新('1'为强制,'0'为可选)。appFileList: 一个包含安装包文件信息的数组,我们取第一个元素的filePath作为下载地址。
- 版本比对与决策 :
- 如果
currentVersion != version,说明有新版本。 - 根据
isforce的值决定后续行为:- 可选更新 (
isforce === '0') : 调用uni.showModal弹出一个友好的确认对话框,将选择权交给用户。 - 强制更新 (
isforce === '1'): 不给用户选择,直接进入下载安装流程。这对于修复严重安全漏洞或兼容性问题至关重要。
- 可选更新 (
- 如果
阶段二:下载与安装 (downloadAndInstallApk)
- 显示加载状态 : 调用
uni.showLoading给用户明确的反馈,告知正在下载新版本。 - 创建并启动下载任务 : 使用
plus.downloader.createDownloadAPI。url: 从上一步获取的 APK 文件下载地址。{ filename: '_doc/update/' }: 指定下载目录。_doc是 UniApp 的一个标准路径别名,指向 App 的私有文档目录,保证了文件的安全性和隔离性。- 回调函数
(downloadedFile, status): 当下载任务完成(无论成功或失败)时触发。
- 处理下载结果 :
- 成功 (
status === 200) : 调用plus.runtime.installAPI 来安装已下载的 APK 文件。{ force: true }: 此参数非常重要。它表示即使要安装的版本号不高于当前版本,也强制进行安装。这在测试回滚或特殊场景下非常有用。- 安装成功回调 : 原代码中注释掉了
plus.runtime.restart()。这是一个非常关键的设计考量。如果在此处重启 App,会再次触发onLaunch生命周期,进而又会调用checkAppUpdate,可能导致无限循环。通常,安装完成后系统会自动处理(如 Android 会弹出安装界面),或者由用户手动重启。
- 失败 (
status !== 200) : 无论是网络问题还是服务器错误,都通过uni.showToast友好地提示用户。
- 成功 (
三、关键点与注意事项
- 平台差异 : 本文代码主要针对 Android 平台 。iOS 由于其封闭的生态,不允许通过此方式安装
.ipa包,除非是企业签名或 TestFlight。因此,这套逻辑通常只在#ifdef APP-ANDROID条件编译块中使用。 - 权限 : 在 Android 上,应用需要拥有
WRITE_EXTERNAL_STORAGE权限才能写入文件。UniApp 通常会在manifest.json中自动处理,但需留意高版本 Android 的分区存储(Scoped Storage)限制。将文件下载到_doc私有目录是规避此问题的最佳实践。 - 用户体验: 强制更新虽然有效,但会中断用户操作,应谨慎使用。可选更新配合醒目的提示(如红点)是更温和的方式。
- 安全性 : 确保
getLastestVersion接口返回的filePath是可信的 HTTPS 地址,防止 APK 被劫持替换。
四、总结
通过巧妙地结合 UniApp 的 plus.runtime 和 plus.downloader API,我们可以轻松构建一套功能完备、用户体验良好的 App 内更新系统。App.vue 中的这段代码结构清晰,逻辑严谨,完美地实现了"检测 -> 决策 -> 下载 -> 安装"的闭环,是 UniApp 开发者处理原生 App 更新需求的经典范例。掌握这套机制,能让你的应用始终保持活力,快速响应市场变化。