uniapp APP自动更新组件

embedded/2024/10/15 22:57:32/

在uniapp中实现APP自动更新功能,主要涉及到客户端在功能不断迭代过程中,需要进行自动更新。uniapp一个详细的实现步骤,包括客户端和服务器端的配置:

服务器端配置

版本信息管理

  1. 服务器端需要维护一个数据库或配置文件,用于存储APP的最新版本信息,包括版本号、更新说明、下载链接等。
  2. 提供一个API接口,客户端可以通过该接口获取最新版本信息。 

客户端实现

  1. 版本信息获取:
    • 在uniapp的客户端,通过uni.request()方法调用服务器端的API接口,获取最新版本信息。
  2. 版本比对:
    • 客户端获取到最新版本信息后,与当前APP的版本号进行比对。
    • 版本号比对逻辑可以根据实际情况设计,常见的做法是比较字符串或将其转换为数字进行比较。
  3. 更新提示:
    • 如果发现新版本,则弹出更新提示框,引导用户进行更新。
    • 可以通过uni.showModal()方法显示更新提示框,并提供更新和取消的选项。
  4. 下载并安装:
    • 用户确认更新后,客户端开始下载新版本APK文件。
    • 下载完成后,使用plus.runtime.install()方法安装APK文件。
    • 注意:安装APK文件需要用户授权,并且可能需要在Android的“设置”中开启“允许安装未知来源的应用”。
  5. 静默更新(可选):
    • 对于一些不需要用户干预的更新,可以考虑实现静默更新。
    • 静默更新通常涉及到wgt(widget)包的更新,而不是整个APK的替换。
    • uniapp提供了相关的插件和API支持wgt包的更新,如uni-upgrade-center等。

组件扩展简化调用

我们只需要在我们的首页引入版本自动更新组件即可。

<template><view class="container container329152"><!-- #ifdef APP --><diy-upgrade style="z-index: 999999999" image="/static/upgrade.png" upgradeUrl=""> </diy-upgrade><!--  #endif --><view class="clearfix"></view></view>
</template><script>export default {data() {return {//用户全局信息userInfo: {},//页面传参globalOption: {},//自定义全局变量globalData: {}};},onShow() {this.setCurrentPage(this);},onLoad(option) {this.setCurrentPage(this);if (option) {this.setData({globalOption: this.getOption(option)});}this.init();},methods: {async init() {}}};
</script><style lang="scss" scoped>.container329152 {}
</style>

组件库代码实现

diy-upgrade组件代码实现,大家如果对此组件库可按需进行二次开发扩展。

<template><view class="mask flex-center" v-if="showUpdate"><view class="content botton-radius" :class="[image?'':'no-imgae']"><view class="content-top" ><view class="content-top-text"><text>{{title}}</text><text class="content-top-text-version">v.{{version}}</text></view><image v-if="image" class="content-top" style="top: 0;" width="100%" height="100%":src='image'></image><view v-else  class="content-top" style="top: 0;" width="100%" height="100%"></view></view><view v-if="image"  class="content-header"></view><view class="content-body"><slot></slot><view class="body" v-if="contents"><scroll-view class="box-des-scroll" scroll-y="true"><rich-text :nodes="contents"></rich-text></scroll-view></view><view class="footer flex-center"><template v-if="isAppStore"><button class="content-button" :style="btnStyle"  style="border: none;color: #fff;" plain @click="jumpToAppStore">{{downLoadBtnTextiOS}}</button></template><template v-else><template v-if="!downloadSuccess"><view class="progress-box flex-column" v-if="downloading"><progress class="progress" border-radius="35" :percent="downLoadPercent":activeColor="btnBgColor" show-info stroke-width="10" /><view class="flex flex-center" style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;"><text>{{downLoadingText}}</text><text>({{downloadedSize}}/{{packageFileSize}}M)</text></view></view><button v-else class="content-button"  :style="btnStyle" style="border: none;color: #fff;" plain@click="updateApp">{{downLoadBtnText}}</button></template><button v-else-if="downloadSuccess && !installed"  :style="btnStyle" class="content-button"style="border: none;color: #fff;" plain :loading="installing" :disabled="installing"@click="installPackage">{{installing ? '正在安装……' : '下载完成,立即安装'}}</button><button v-if="installed && isWGT" :style="btnStyle" class="content-button" style="border: none;color: #fff;" plain@click="restart">安装完毕,点击重启</button></template></view></view><text v-if="!is_mandatory" class="close-img diy-icon-close" @click.stop="closeUpdate"></text></view></view>
</template><script>const localFilePathKey = 'UNI_ADMIN_UPGRADE_CENTER_LOCAL_FILE_PATH'const platform_iOS = 'iOS';let downloadTask = null;let openSchemePromise/*** 对比版本号,如需要,请自行修改判断规则* 支持比对	("3.0.0.0.0.1.0.1", "3.0.0.0.0.1")	("3.0.0.1", "3.0")	("3.1.1", "3.1.1.1") 之类的* @param {Object} v1* @param {Object} v2* v1 > v2 return 1* v1 < v2 return -1* v1 == v2 return 0*/function compare(v1 = '0', v2 = '0') {v1 = String(v1).split('.')v2 = String(v2).split('.')const minVersionLens = Math.min(v1.length, v2.length);let result = 0;for (let i = 0; i < minVersionLens; i++) {const curV1 = Number(v1[i])const curV2 = Number(v2[i])if (curV1 > curV2) {result = 1break;} else if (curV1 < curV2) {result = -1break;}}if (result === 0 && (v1.length !== v2.length)) {const v1BiggerThenv2 = v1.length > v2.length;const maxLensVersion = v1BiggerThenv2 ? v1 : v2;for (let i = minVersionLens; i < maxLensVersion.length; i++) {const curVersion = Number(maxLensVersion[i])if (curVersion > 0) {v1BiggerThenv2 ? result = 1 : result = -1break;}}}return result;}export default {props: {//更新图片image: {type: String,default: ''},//版本更新较验地址upgradeUrl:{type: String,default: ''},// 进度条颜色btnBgColor:{default: '',type: String}},data() {return {showUpdate:false,// 更新的版本号version: '',// 系统环境platform: '',// 下载链接url: '',// 跳转的应用市场列表storeList: [],type:'',// 从之前下载安装installForBeforeFilePath: '',// 安装installed: false,installing: false,// 下载downloadSuccess: false,downloading: false,downLoadPercent: 50,downloadedSize: 0,packageFileSize: 0,tempFilePath: '', // 要安装的本地包地址// 默认安装包信息title: '版本更新',contents: '',is_mandatory: false,// 可自定义属性downLoadBtnTextiOS: '立即跳转更新',downLoadBtnText: '立即下载更新',downLoadingText: '安装包下载中,请稍后',pageLevelNum: 0}},onBackPress() {// 强制更新不允许返回if (this.is_mandatory) {return true}downloadTask && downloadTask.abort()},onHide() {openSchemePromise = null},mounted() {this.init()},computed: {isWGT() {return this.type === 'wgt'},isiOS() {return !this.isWGT ? this.platform.includes(platform_iOS) : false;},isAppStore() {return this.isiOS || (!this.isiOS && !this.isWGT && this.url.indexOf('.apk') === -1)},btnStyle(){return this.btnBgColor?{background:this.btnBgColor}:{}}},methods: {// 获取更新内容片段getContentHTML(content) {let contentArr = content.split('\n');return contentArr.map(item => `<p>${item}</p>`).join('\n')},async init(){// #ifdef APP-PLUSlet thiz = this;if(!thiz.upgradeUrl){console.log('请配置版本较验地址')console.log("{url:'你的APK下越地址',version:'1.0.1',title:'版本更新',contents:'版本更新内容'}")uni.showToast({title:'请配置版本较验地址'})return;}uni.getSystemInfo({success: (res) => {let platform = res.platform;// 获取本机版本号plus.runtime.getProperty(plus.runtime.appid,async (wgtinfo) => {thiz.versionCode = wgtinfo.versionCode;let res = await getApp().globalData.currentPage.$http.post(thiz.upgradeUrl,{appid: plus.runtime.appid,platform,version: plus.runtime.version,wgtVersion: wgtinfo.version})res = res.data;console.log(res)//如果API返回新的地址,并判断版本是否相同if(res.url){thiz.url = res.urlthiz.version = res.version//判断API返回的版本是不是大于系统版本if(compare(thiz.version,wgtinfo.version)){// 跳转的应用市场列表thiz.storeList = res.store_list || [];thiz.title = res.title || '发现新版本';thiz.type = res.type;if(res.contents){thiz.contents =  thiz.getContentHTML(res.contents)}thiz.is_mandatory = res.is_mandatory||falsethis.checkLocalStoragePackage()}}});},fail(e){console.log(e)}});// #endif},goBack() {this.showUpdate = false},checkLocalStoragePackage() {// 如果已经有下载好的包,则直接提示安装const localFilePathRecord = uni.getStorageSync(localFilePathKey)if (localFilePathRecord) {const {version,savedFilePath,installed} = localFilePathRecord// 比对版本if (!installed && compare(version, this.version) === 0) {this.downloadSuccess = true;this.installForBeforeFilePath = savedFilePath;this.tempFilePath = savedFilePath} else {// 如果保存的包版本小 或 已安装过,则直接删除this.deleteSavedFile(savedFilePath)}}this.showUpdate = true;},async closeUpdate() {if (this.downloading) {if (this.is_mandatory) {return uni.showToast({title: '下载中,请稍后……',icon: 'none',duration: 500})}uni.showModal({title: '是否取消下载?',cancelText: '否',confirmText: '是',success: res => {if (res.confirm) {downloadTask && downloadTask.abort()this.goBack()}}});return;}if (this.downloadSuccess && this.tempFilePath) {// 包已经下载完毕,稍后安装,将包保存在本地await this.saveFile(this.tempFilePath, this.version)this.goBack()return;}this.goBack()},updateApp() {this.checkStoreScheme().catch(() => {this.downloadPackage()})},// 跳转应用商店checkStoreScheme() {const storeList = (this.store_list || []).filter(item => item.enable)if (storeList && storeList.length) {storeList.sort((cur, next) => next.priority - cur.priority).map(item => item.scheme).reduce((promise, cur, curIndex) => {openSchemePromise = (promise || (promise = Promise.reject())).catch(() => {return new Promise((resolve, reject) => {plus.runtime.openURL(cur, (err) => {reject(err)})})})return openSchemePromise}, openSchemePromise)return openSchemePromise}return Promise.reject()},downloadPackage() {this.downloading = true;//下载包downloadTask = uni.downloadFile({url: this.url,success: res => {if (res.statusCode == 200) {this.downloadSuccess = true;this.tempFilePath = res.tempFilePath// 强制更新,直接安装if (this.is_mandatory) {this.installPackage();}}},complete: () => {this.downloading = false;this.downLoadPercent = 0this.downloadedSize = 0this.packageFileSize = 0downloadTask = null;}});downloadTask.onProgressUpdate(res => {this.downLoadPercent = res.progress;this.downloadedSize = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);});},installPackage() {// #ifdef APP-PLUS// wgt资源包安装if (this.isWGT) {this.installing = true;}plus.runtime.install(this.tempFilePath, {force: false}, async res => {this.installing = false;this.installed = true;// wgt包,安装后会提示 安装成功,是否重启if (this.isWGT) {// 强制更新安装完成重启if (this.is_mandatory) {uni.showLoading({icon: 'none',title: '安装成功,正在重启……'})setTimeout(() => {uni.hideLoading()this.restart();}, 1000)}} else {const localFilePathRecord = uni.getStorageSync(localFilePathKey)uni.setStorageSync(localFilePathKey, {...localFilePathRecord,installed: true})}}, async err => {// 如果是安装之前的包,安装失败后删除之前的包if (this.installForBeforeFilePath) {await this.deleteSavedFile(this.installForBeforeFilePath)this.installForBeforeFilePath = '';}// 安装失败需要重新下载安装包this.installing = false;this.installed = false;uni.showModal({title: '更新失败,请重新下载',content: err.message,showCancel: false});});// 非wgt包,安装跳出覆盖安装,此处直接返回上一页if (!this.isWGT && !this.is_mandatory) {this.goBack()}// #endif},restart() {this.installed = false;// #ifdef APP-PLUS//更新完重启appplus.runtime.restart();// #endif},saveFile(tempFilePath, version) {return new Promise((resolve, reject) => {uni.saveFile({tempFilePath,success({savedFilePath}) {uni.setStorageSync(localFilePathKey, {version,savedFilePath})},complete() {resolve()}})})},deleteSavedFile(filePath) {uni.removeStorageSync(localFilePathKey)return uni.removeSavedFile({filePath})},jumpToAppStore() {plus.runtime.openURL(this.url);}}}
</script><style>page {background: transparent;}.flex-center {/* #ifndef APP-NVUE */display: flex;/* #endif */justify-content: center;align-items: center;}.mask {position: fixed;left: 0;top: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, .65);}.botton-radius {border-radius: 30rpx;}.content {position: relative;top: 0;width: 600rpx;background-color: #fff;box-sizing: border-box;padding: 0 50rpx;font-family: Source Han Sans CN;}.text {/* #ifndef APP-NVUE */display: block;/* #endif */line-height: 200px;text-align: center;color: #FFFFFF;}.content-top {position: absolute;top: -195rpx;left: 0;width: 600rpx;height: 270rpx;}.content-top-text {font-size: 45rpx;font-weight: bold;color: #F8F8FA;position: absolute;top: 120rpx;left: 50rpx;z-index: 1;.content-top-text-version{font-size: 24rpx;}}.no-imgae .content-top{padding-top:30px;height: auto;}.no-imgae .content-top,.no-imgae .content-top .content-top-text{position: relative;top:0;left:0;color:#000;width:100%;}.content-header {height: 70rpx;}.title {font-size: 33rpx;font-weight: bold;color: #3DA7FF;line-height: 38px;}.footer {height: 150rpx;display: flex;align-items: center;justify-content: space-around;}.box-des-scroll {box-sizing: border-box;min-height: 100rpx;max-height: 400rpx;text-align: left;}.box-des {font-size: 26rpx;color: #000000;line-height: 50rpx;}.progress-box {width: 100%;}.progress {width: 90%;height: 40rpx;border-radius: 35px;}.close-img {width: 70rpx;height: 70rpx;z-index: 1000;position: absolute;bottom: -120rpx;left: calc(50% - 70rpx / 2);font-size: 60rpx;color:#fff;}.content-button {text-align: center;flex: 1;font-size: 30rpx;font-weight: 400;color: #FFFFFF;border-radius: 40rpx;margin: 0 18rpx;height: 80rpx;line-height: 80rpx;background: linear-gradient(to right, #1785ff, #3DA7FF);}.content-button.button-hover {transform: translate(1rpx, 1rpx);}.flex-column {display: flex;flex-direction: column;align-items: center;}
</style>


http://www.ppmy.cn/embedded/118673.html

相关文章

Python编码系列—Python观察者模式:实现事件驱动架构的利器

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

001、restful设计规范

https://www.kancloud.cn/kancloud/rest-api-design-safety/78113 https://www.kancloud.cn/kancloud/http-api-design/78123 https://www.kancloud.cn/kancloud/http-api-guide/56268 restful接口设计规范 按照restful接口设计规范 GET &#xff08;SELECT&#xff09;&…

安装软件及apt install -f修复均报错

UOS统信安装软件过程及修复依赖过程&#xff0c;可排查deepin-installer和dpkg问题 文章目录 一、问题现象二、问题原因三、解决方案 一、问题现象 执行apt install -f 都会出现该报错&#xff0c;如图所示&#xff1a; 二、问题原因 造成这种情况的原因在于/var/lib/dpkg/…

Spring JDBC及声明式事务

目录 Spring JDBC基础概念 Spring声明式事务 事务传播方式 Spring JDBC基础概念 Spring JDBC 封装了原生的JDBC API&#xff0c;使得处理关系型数据库更加简单。Spring JDBC的核心是JdbcTemplate&#xff0c;里面封装了大量数据库CRUD的操作。使用Spring JDBC…

自己开发一个网站系列之-从基础到高级

自己开发一个网站系列之-从基础到高级 在上一篇博客中&#xff0c;我们介绍了网页开发的基础知识&#xff0c;包括HTML、CSS和JavaScript的基本用法。这是一个很好的起点&#xff0c;但如果你想进一步提升自己的技能&#xff0c;掌握更多高级概念和技术&#xff0c;就必须深入…

【CSS】字体文本

color 颜色font-size 大小font-family 字体font-style 样式font-weight 加粗text-decoration 下划线text-shadow 阴影text-transform 大小写变换text-indent 缩进text-align 水平对齐 、vertical-align垂直对齐text-overflow 溢出word-wrap 换行word-break 截断white-space 空白…

基于深度学习的虚拟环境生成

基于深度学习的虚拟环境生成是利用深度学习技术自动创建复杂的三维虚拟场景和环境。这一领域在游戏开发、虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;、模拟训练等方面有广泛的应用。以下是这一领域的主要技术和方法概述&#xff1a; 1. 生成对抗…

MATLAB Function模块用法案例

Simulink中的MATLAB Function模块是一个非常灵活的工具&#xff0c;允许用户直接在Simulink模型中嵌入MATLAB代码&#xff0c;以实现自定义的算法或功能 MATLAB Function模块的基本用法 添加模块&#xff1a; 在Simulink的模型窗口中&#xff0c;通过搜索“MATLAB Function”…