UniApp - 在线更新(热更新 | 整包更新)

最近做了一个 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>

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#abstract.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值