標簽: uni-app 版本更新
前情
uni-app是我很喜歡的跨平台框架,它能開發小程序,H5,APP(安卓/iOS),對前端開發很友好,自帶的IDE讓開發體驗也很棒,公司項目就是主推uni-app。而是app版本更新是最基本的功能,特記錄整個踩坑過程。
版本更新
更新主邏輯
在每次app啟動並登錄成功的時候做一個版本檢測,如果當前版本小於服務端配置的版本號,服務端會告訴是否有更新數據(此處為res.updateFlag,true為需要更新),如果有的話會告訴我去哪里(此次為res下的url)拿更新包數據,客戶端再請求res.url拿到更新相關數據(更新包的包地址),如果有更新包地址則彈出彈窗提示用戶去更新。
安裝更新邏輯為當返回數據url是熱更新地址時,則執行熱更新邏輯,如果不是則直接調用瀏覽器去下載安裝。
更新關鍵代碼
function checkVersion() {
// #ifdef APP-PLUS
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
let currVersion = widgetInfo.version;
// 提交版本號和當前appCode,appcode主要是因為我們多個app共用了一套服務端,傳此參數是為了告訴服務是哪個app在請求更新
const params = { "appVersionNo": currVersion, "appCode": APPCODE};
// 更新判斷接口請求,
uni.request({
url: "更新判斷接口地址",
data: params,
success: function(res) {
// console.log("----更新檢測數據----:", res);
if (res.updateFlag) {
updateShowModel(res.url, currVersion)
}
}
});
});
// #endif
}
/**
* 更新提示
* @param {string} url
* @param {string} currVersion
*/
function updateShowModel(url, currVersion) {
uni.request({ url, success: json => {
//console.log("----當前從服務端拉取的版本信息----:", json);
if (!json || !json.data || !json.data.versions || !json.data.versions[0]) {
return;
}
const versionData = json.data
const versionInfo = versionData.versions[0];
const forcedVersion = versionData.lastForceVersion;
// 是否強制更新,只有當前版本小於服務端返回的強制更新版本號隱藏處彈窗的取消按鈕來達到要求用戶強制更新
const isForcedUpdate = versionCompare(currVersion, forcedVersion) == -1;
//console.log("----更新相關數據----:",versionData, isForcedUpdate,currVersion,forcedVersion);
uni.showModal({
title: `發現新版本v${versionInfo.version}`,
content: versionInfo.details.join('\n'),
showCancel: !isForcedUpdate,
success: function (res) {
if (res.confirm) {
updateApp(versionData)
//console.log('用戶點擊確定');
} else if (res.cancel) {
//console.log('用戶點擊取消');
}
}
});
}})
}
/**
* 執行更新操作
* @param {Object} versionData
*/
function updateApp(versionData) {
// 如果有熱更新配置文件,則熱更
if (versionData.wgtUrl) {
startAndroidUpdate(versionData);
return;
}
switch (uni.getSystemInfoSync().platform) {
case 'android':
// 安卓安裝包的apk地址
plus.runtime.openURL(versionData.url);
break
case 'ios':
// ios需要特殊處理,需要服務端給一個plist地址
plus.runtime.openURL(`itms-services://?action=download-manifest&url=${versionData.url}`);
break
}
}
/**
* 自動下載安裝更新
* @param {Object} versionData 安裝包地址相關信息
*/
var showLoading;
function startAndroidUpdate(versionData) {
// 熱更新的wgt文件
let url = versionData.wgtUrl;
var dtask = plus.downloader.createDownload(
url, {method:"GET",filename:`_downloads/${APPID}.wgt`},
function(d, status) {
// 下載完成
if (status == 200) {
//console.log("----安裝包文件地址----:",d, plus.io.convertLocalFileSystemURL(d.filename), d.filename);
//plus.nativeUI.toast("軟件開始安裝重啟");
showLoading.setTitle(" 安裝重啟中... ");
plus.runtime.install(d.filename, {force: true}, e => {
plus.nativeUI.closeWaiting();
plus.runtime.restart();
}, function(error) {
console.log("----安裝失敗01----:", error);
handleWgtInstallFail(versionData);
})
} else {
console.log("----安裝失敗02----:", d, status);
handleWgtInstallFail(versionData);
}
});
try {
var progress = 0;
showLoading = plus.nativeUI.showWaiting(" 開始下載 "); //創建一個showWaiting對象
dtask.start(); // 開啟下載的任務
dtask.addEventListener('statechanged', function(
task,
status
) {
// 給下載任務設置一個監聽 並根據狀態 做操作
switch (task.state) {
case 1:
showLoading.setTitle(" 正在下載 0% ");
break;
case 2:
showLoading.setTitle("已連接到服務器");
break;
case 3:
progress = parseInt((parseFloat(task.downloadedSize) / parseFloat(task.totalSize)) * 100);
showLoading.setTitle(" 正在下載" + String(progress).padStart(3, " ") + "% ");
break;
case 4:
//plus.nativeUI.closeWaiting();
//下載完成
break;
}
});
} catch (err) {
plus.nativeUI.closeWaiting();
console.log("----安裝失敗03----:", err);
handleWgtInstallFail(versionData);
}
}
/**
* 熱更新失敗處理
*
*/
function handleWgtInstallFail(versionInfo) {
plus.nativeUI.closeWaiting();
uni.showModal({
title: "溫馨提示",
content: "自動更新失敗,是否手動更新",
success: function (res) {
if (res.confirm) {
plus.runtime.openURL(versionInfo.url);
}
}
});
}
/**
* 版本號比較
* @param {Object} v1 當前版本
* @param {Object} v2 強制更新版本
*/
function versionCompare(v1, v2) {
var GTR = 1; //大於
var LSS = -1; //小於
var EQU = 0; //等於
var v1arr = String(v1).split(".").map(function (a) {
return parseInt(a);
});
var v2arr = String(v2).split(".").map(function (a) {
return parseInt(a);
});
var arrLen = Math.max(v1arr.length, v2arr.length);
var result;
//排除錯誤調用
if (v1 == undefined || v2 == undefined) {
throw new Error();
}
//檢查空字符串,任何非空字符串都大於空字符串
if (v1.length == 0 && v2.length == 0) {
return EQU;
} else if (v1.length == 0) {
return LSS;
} else if (v2.length == 0) {
return GTR;
}
//循環比較版本號
for (var i = 0; i < arrLen; i++) {
result = xxcanghaiComp(v1arr[i], v2arr[i]);
if (result == EQU) {
continue;
} else {
break;
}
}
return result;
function xxcanghaiComp(n1, n2) {
if (typeof n1 != "number") {
n1 = 0;
}
if (typeof n2 != "number") {
n2 = 0;
}
if (n1 > n2) {
return GTR;
} else if (n1 < n2) {
return LSS;
} else {
return EQU;
}
}
}
注意事項
開發過程中在ios下熱更是可以的,但是在安卓下一直報如下錯誤:
{
"code": -1201,
"message": "WGT/WGTU文件格式錯誤"
}
官方論壇也有人遇到,試了很多解決方法都不行,通過打日志發現安卓下下載下來的包是.bin文件。而不是熱更需要的wgt文件,一開始懷疑是包沒下載完,但明顯是在下載完的回調里處理的,后面想到能不能手動改成.wgt文件,plus.downloader.createDownload第二個參數可以指定下載包的文件名,就這樣解決了這個問題。
其實安卓下拿到apk地址是可以不需要通過調用瀏覽器去下載的,直接走熱更新邏輯也是可以的。