UNIAPP实现APP自动更新
整体思路和API使用
工作流程
- App 启动时检查更新
 - 发现新版本时显示更新提示
 - 如果是强制更新,用户必须更新
 - 下载完成后自动安装
 
API
getVersion:自己服务器API,返回版本号、下载地址等信息plus.runtime.getProperty:获取APP当前版本号uni.downloadFile:下载文件plus.runtime.install:安装软件downloadTask.onProgressUpdate:监听下载进度
具体实现
后端getVersionAPI代码
// Version.java
@Data
public class Version {
private String version;      // 版本号
private String downloadUrl;  // 下载地址
private String description; // 更新说明
private boolean forceUpdate; // 是否强制更新
}
// VersionController.java
@RestController
@RequestMapping("/api/version")
public class VersionController {
@GetMapping("/check")
public Result checkVersion(@RequestParam String currentVersion) {
Version version = new Version();
        version.setVersion("1.1.7");  // 最新版本号
        version.setDownloadUrl("软件下载地址"); // 下载地址
        version.setDescription("1. 修复已知问题\n2. 新增功能");
        version.setForceUpdate(true);  // 是否强制更新
// 比较版本号
if (compareVersion(currentVersion, version.getVersion()) < 0) {
return Result.success(version);
        }
return Result.success(null);
    }
// 版本号比较方法
private int compareVersion(String v1, String v2) {
        String[] version1 = v1.split("\\.");
        String[] version2 = v2.split("\\.");
int i = 0;
while (i < version1.length && i < version2.length) {
int num1 = Integer.parseInt(version1[i]);
int num2 = Integer.parseInt(version2[i]);
if (num1 < num2) return -1;
else if (num1 > num2) return 1;
            i++;
        }
if (version1.length < version2.length) return -1;
if (version1.length > version2.length) return 1;
return 0;
    }
}
其中Version类可以写到数据库中获取
前端update.js封装
// 版本更新工具类 - 使用单例模式确保全局只有一个更新实例
import {
	check
} from "../api/util/util";
class AppUpdate {
constructor() {
// 当前应用版本号
this.currentVersion = '';
// 服务器返回的更新信息
this.updateInfo = null;
    }
// 检查更新方法
checkUpdate() {
//仅在app环境下运行
// #ifdef APP-PLUS 
        plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
this.currentVersion = widgetInfo.version;
console.log('当前版本:' + this.currentVersion);
check(this.currentVersion).then(res => {
if (res.data.data) {
this.updateInfo = res.data.data;
this.showUpdateDialog();
		}
            })
            .catch(err => {
console.log(err);
            });
        });
// #endif
	}
showUpdateDialog() {
		uni.showModal({
title: '发现新版本',
content: this.updateInfo.description,
confirmText: '立即更新',
cancelText: '稍后再说',
showCancel: !this.updateInfo.forceUpdate, // 强制更新时禁止取消
success: (res) => {
if (res.confirm) {
this.downloadApp();
				} else if (this.updateInfo.forceUpdate) {
					plus.runtime.quit();
				}
			}
		});
	}
downloadApp() {
/* uni.showLoading({
			title: '下载中...',
			mask: true // 添加遮罩防止重复点击
		}); */
// 先打印下载地址,检查 URL 是否正确
console.log('下载地址:', this.updateInfo.downloadUrl);
let showLoading=plus.nativeUI.showWaiting('正在下载');
const downloadTask = uni.downloadFile({
url: this.updateInfo.downloadUrl,
success: (res) => {
console.log('下载结果:', res); // 添加日志
if (res.statusCode === 200) {
console.log('开始安装:', res.tempFilePath); // 添加日志
			plus.runtime.install(
				res.tempFilePath, {
force: false
				},
() => {
console.log('安装成功'); // 添加日志
					 plus.nativeUI.closeWaiting();
					plus.runtime.restart();
				},
(error) => {
console.error('安装失败:', error); // 添加错误日志
						 plus.nativeUI.closeWaiting();
						uni.showToast({
title: '安装失败: ' + error.message,
icon: 'none',
duration: 2000
					});
				}
                    );
		} else {
console.error('下载状态码异常:', res.statusCode); // 添加错误日志
			 plus.nativeUI.closeWaiting();
			uni.showToast({
title: '下载失败: ' + res.statusCode,
icon: 'none',
duration: 2000
			});
		}
	},
fail: (err) => {
console.error('下载失败:', err); // 添加错误日志
		 plus.nativeUI.closeWaiting();
		uni.showToast({
title: '下载失败: ' + err.errMsg,
icon: 'none',
duration: 2000
		});
	}
});
//监听下载进度
downloadTask.onProgressUpdate((res) => {
console.log('下载进度:', res.progress); // 添加进度日志
if (res.progress > 0) { // 只在有实际进度时更新提示
		showLoading.setTitle('正在下载'+res.progress+'%');
	}
    });
	}
}
//单例模式实现
let instance = null;
export default {
getInstance() {
if (!instance) {
			instance = new AppUpdate();
		}
return instance;
	}
}
注意:如果直接使用uni.showLoading来显示下载进度,会造成闪烁效果,所以这里用let showLoading=plus.nativeUI.showWaiting('正在下载'); 
引用js
以app.vue为例,在启动时触发检查更新
import AppUpdate from '@/utils/update.js';
export default {
onLaunch: function() {
// #ifdef APP-PLUS
AppUpdate.getInstance().checkUpdate();
// #endif
    }
}
在 manifest.json 中配置权限
{
"app-plus": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>"
                ]
            }
        }
    }
}
这样封装的优点
- 代码更加模块化
 - 可以在任何地方调用
 - 使用单例模式避免重复创建
 - 更容易维护和扩展
 
作者:HuoWang
来源:juejin.cn/post/7457206505021341730
                            来源:juejin.cn/post/7457206505021341730