最近做了一个 App 更新的需求,在 web 端我们只需要在站点内重新上传打包好的文件就行,但是如果混合开发了 App 端,我们更新了一些静态资源后,那就需要使用
热更新
或者整包更新
了。
1.热更新和整包更新
热更新
:无需重新安装App,只需要下载最新的 wgt 包即可更新,在未修改 SDK、原生插件
时,可以使用该方式更新。
wgt
:所有的前端资源文件压缩包。
整包更新
:相当于迭代一个版本吧,需要重新安装App。
2.版本名称和版本号
在 manifest.json
中,versionName
代表版本名称,versionCode
代表版本号,版本名称用户是可见的,例如:v1.0.0。
我使用的方案是:版本名称发生改变代表整包更新,versionCode发生改变代表热更新。
当整包更新后,将 versionCode 置 1,因为整包更新的时候已经是最新的资源了,不需要以前热更新的数据了。
3.代码实现
以下片段是:获取版本信息接口
返回的信息,data 下分别是热更新和整包更新的配置,其中 isForce 代表是否强制更新。
"data": {
"appRefreshConfig": {
"versionName": "1.0.0",
"downloadUrl": "https://www.xx/update/xx.apk",
"versionDesc": "测试更新.",
"isForce": 0,
"type": "appRefreshConfig"
},
"hotRefreshConfig": {
"versionCode": "2",
"downloadUrl": "https://www.xx/update/2.wgt",
"versionDesc": "修复了xxx的问题.",
"isForce": 1,
"type": "hotRefreshConfig"
}
}
接下来可以创建一个 store,在里面去编写具体的逻辑代码,下面是我封装好的 store 代码,可以直接使用。
// 获取配置版本的接口
import { getServerVersionInfoApi } from "@/api/utils.js";
export default {
namespaced: true,
state: {
// 本地信息
localVersionInfo: { versionCode: "", versionName: "" },
// 服务器信息
serverVersionInfo: {
// 热更新配置项
hotRefreshConfig: { versionCode: "", versionDesc: "", isForce: undefined, downloadUrl: "" },
// 整包更新配置项
appRefreshConfig: { versionName: "", versionDesc: "", isForce: undefined, downloadUrl: "" },
},
// 页面提示内容
pageShowInfo: {
type: "", // hotRefreshConfig | appRefreshConfig
downloadTempPath: "", // 下载资源的临时路径
isNeed: false, // 是否需要更新(是否跳转到更新页面的关键字段)
},
// 安装按钮配置
installShowInfo: {
text: "开始下载",
},
},
actions: {
/**
* 获取本地应用版本信息。
*/
async getLocalVersionInfo({ commit }) {
return new Promise((resolve, reject) => {
plus.runtime.getProperty("这里写你的AppID", (widgetInfo) => {
commit("setLocalVersionInfo", { versionCode: widgetInfo.versionCode, versionName: widgetInfo.version });
resolve({ versionCode: widgetInfo.versionCode, versionName: widgetInfo.version });
});
});
},
/**
* 获取服务器版本信息。
* @returns {Promise<{
* hotRefreshConfig: { versionCode: string, versionDesc: string, isForce: Number, downloadUrl: string },
* appRefreshConfig: { versionName: string, versionDesc: string, isForce: Number, downloadUrl: string }
* }>} hotRefreshConfig: 热更新配置,appRefreshConfig: app更新配置
*/
async getServerVersionInfo({ commit }) {
try {
const getServerVersionInfoApiRes = await getServerVersionInfoApi();
commit("setServerVersionInfo", getServerVersionInfoApiRes.data);
return Promise.resolve(getServerVersionInfoApiRes.data);
} catch (e) {
uni.showToast({ title: "获取版本信息失败", icon: "none" });
return Promise.reject(getServerVersionInfoApiRes.message);
}
},
/**
* 对比 (本地版本信息 和 服务器版本信息), 并进行更新提醒。
* - 先对比整包版本名称(versionName), 如果本地版本小于服务器版本, 则直接更新主包.
* - 否则 对比版本号(versionCode), 如果本地版本小于服务器版本, 则热更新.
* @param {Object} options 选项对象,包含是否显示最新版本提示的设置。
* @param {boolean} [options.latestVerIsTip] 是否在本地版本为最新时显示提示信息,默认为 false。
*/
async comparison({ commit, dispatch }, { latestVerIsTip = false }) {
// 本地信息
const localVersionInfo = await dispatch("getLocalVersionInfo");
const serverVersionInfo = await dispatch("getServerVersionInfo");
// 对比整包
if (localVersionInfo.versionName < serverVersionInfo.appRefreshConfig.versionName) {
return commit("setPageShowInfoType", "appRefreshConfig");
}
// 如果整包不需要更新, 则对比热更新包
if (localVersionInfo.versionName >= serverVersionInfo.appRefreshConfig.versionName) {
// 对比热更新包
if (localVersionInfo.versionCode < serverVersionInfo.hotRefreshConfig.versionCode) {
return commit("setPageShowInfoType", "hotRefreshConfig");
}
}
// 提示无需更新
if (latestVerIsTip) {
uni.showToast({ title: "您当前为最新版本!", icon: "none" });
}
},
// 点击安装按钮的事件
installClickHandle({ state, commit, dispath }) {
// 如果在下载中点击, 无效
if (state.installShowInfo.text.includes("下载中", "%")) return;
// 如果下载完成后点击确认按钮, 弹出安装界面
if (state.installShowInfo.text.includes("下载完成")) {
return plus.runtime.install(state.pageShowInfo.downloadTempPath, { force: true });
}
commit("setInstallShowInfoKey", { key: "text", val: "下载中" });
// 获取当前更新的类型
const type = state.pageShowInfo.type;
// 创建下载对象
const downloadContext = plus.downloader.createDownload(state.serverVersionInfo[type].downloadUrl, {}, (download, status) => {
if (status == 200) {
commit("setPageShowInfoKey", { key: "downloadTempPath", val: download.filename });
commit("setInstallShowInfoKey", { key: "text", val: "下载完成" });
// 进行安装
plus.runtime.install(download.filename, { force: true }, (widgetInfo) => {
// 退出应用
if (state.pageShowInfo.type === "hotRefreshConfig") {
uni.showModal({
title: "更新完成",
content: "为了加载成功, 请点击确定后重新打开App!",
showCancel: false,
success: () => {
// plus.runtime.restart(); -> 使用 restart() 可以重启应用 | 但是有可能卡在加载页.
plus.runtime.quit(); // 退出应用, 让用户重新打开.
},
});
}
});
} else {
uni.showToast({ title: "下载失败", icon: "none" });
commit("setInstallShowInfoKey", { key: "text", val: "重新下载" });
}
});
// 添加下载监听器
downloadContext.addEventListener("statechanged", (download, status) => {
// 处理除数不能是0的问题.
if (download.totalSize === 0 || download.downloadedSize === 0) {
return commit("setInstallShowInfoKey", { key: "text", val: "0%" });
}
commit("setInstallShowInfoKey", {
key: "text",
val: `${parseFloat((download.downloadedSize / download.totalSize) * 100).toFixed(1)}%`,
});
if (download.state == 4 && status == 200) commit("setInstallShowInfoKey", { key: "text", val: "下载完成" });
});
// 开始下载
downloadContext.start();
},
},
mutations: {
// 设置 installShowInfo[key]
setInstallShowInfoKey(state, { key, val }) {
state.installShowInfo[key] = val;
},
// 设置 pageShowInfo.type
setPageShowInfoType(state, val) {
state.pageShowInfo["type"] = val;
state.pageShowInfo["isNeed"] = true;
},
// 设置 pageShowinfo[key]
setPageShowInfoKey(state, { key, val }) {
state.pageShowInfo[key] = val;
},
// 设置 serverVersionInfo
setServerVersionInfo(state, val) {
state.serverVersionInfo = val;
},
// 设置的 localVersionInfo
setLocalVersionInfo(state, val) {
state.localVersionInfo = val;
},
},
};
然后在进入页面时,需要去 App.vue
中,触发对比方法。
<script>
export default {
onLaunch: function () {
// #ifdef APP-PLUS
this.$store.dispatch("detectionUpdate/comparison", {}); // 检测更新
// #endif
},
watch: {
// 如果 isNeed 为 true, 则说明有新版本, 直接跳转到更新页面.
"$store.state.detectionUpdate.pageShowInfo.isNeed": (n, o) => {
if (n) uni.navigateTo({ url: "/pages/update/index" });
},
},
};
</script>
然后创建一个更新页面:/pages/update/index
,先来配置 pages.json,主要是设置一下样式。
{
"path": "pages/update/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"app-plus": {
"bounce": "none",
"animationType":"none",
"background": "transparent"
}
}
}
然后在 update/index.vue 中去编写逻辑代码,这里的 u-modal 组件是 uview
组件库中的,然后就大功告成了!
<template>
<div class="updatePageContainer">
<u-modal
:show="true"
:title="getUpdateInfo.title"
:content="getUpdateInfo.desc"
:confirmText="getBtnText"
@confirm="updateClickHandle"
:showCancelButton="!isForce"
cancelText="暂不安装"
@cancel="updateCancelHandle"
>
</u-modal>
</div>
</template>
<script>
export default {
name: "Update",
// 如果强制更新, 就禁用返回按键
onBackPress(options) {
if (this.isForce) {
if (options.from == "backbutton") return true;
}
},
methods: {
// 取消更新
updateCancelHandle() {
uni.navigateBack();
},
// 点击更新按钮
async updateClickHandle() {
this.$store.dispatch("detectionUpdate/installClickHandle");
},
},
computed: {
// 当前是否强制更新: { true:强制, false:不强制 }
isForce() {
const type = this.$store.state.detectionUpdate.pageShowInfo.type;
return this.$store.state.detectionUpdate.serverVersionInfo[type].isForce === 1;
},
// 获取当前更新信息
getUpdateInfo() {
const type = this.$store.state.detectionUpdate.pageShowInfo.type;
const updateInfo = this.$store.state.detectionUpdate.serverVersionInfo[type];
return {
title: `发现新版本~${type === "hotRefreshConfig" ? "(免安装)" : ""}`,
desc: updateInfo.versionDesc,
};
},
// 获取按钮的文字
getBtnText() {
return this.$store.state.detectionUpdate.installShowInfo.text;
},
},
};
</script>
<style lang="scss">
page {
background: rgba(0, 0, 0, 0.5);
}
</style>
评论 (1)
写完博客又看了一眼代码,其实没有必要在App.vue中去 watch 这个 isNeed 属性。
在修改 仓库中type 属性的时候去执行跳转页面就好了,感觉像脱了裤子放了个屁一样😂。