UNIAPP实现APP自动更新

2,872 阅读2分钟

整体思路和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\"/>"
                ]
            }
        }
    }
}

这样封装的优点

  • 代码更加模块化

  • 可以在任何地方调用

  • 使用单例模式避免重复创建

  • 更容易维护和扩展