Uniapp 实现app自动检测更新/自动更新功能

实现步骤

  1. 配置 manifest.json
    • 在 manifest.json 中设置应用的基本信息,包括 versionName 和 versionCode

           一般默认0.0.1,1. 

  1. 服务器端接口开发
    • 提供一个 API 接口,返回应用的最新版本信息,版本号、下载链接。
  2. 客户端检测更新
    • 使用 uni.request 发送请求到服务器端接口,获取最新版本信息。
    • 对比本地版本与服务器版本,判断是否需要更新。
  3. 展示更新提示
    • 如果需要更新,使用 uni.showModal 方法展示更新提示。
  4. 处理用户选择
    • 用户选择更新后,调用plus.downloader.createDownload 方法下载新版本。
    • 监听下载进度,并在下载完成后调用 plus.runtime.install 安装新版本。
  5. 异常处理
    • 对可能出现的错误进行捕获和处理,确保良好的用户体验。

我是参考的一个插件把过程简化了一些

插件地址:https://ext.dcloud.net.cn/plugin?id=9660


 

我简化了作者的index.js文件。其他的没变,以下是我的完整方法。

一共三个JS文件,注意引入路径。

 index.vue

import appDialog from '@/uni_modules/app-upgrade/js_sdk/dialog';
onLoad(){
    // 检查更新
    this.checkForUpdate()
},
methods: {
    async checkForUpdate() {
        //模拟接口返回数据
        let Response = {
            status: 1,// 0 无新版本 | 1 有新版本
            latestVersionCode: 200,//接口返回的最新版本号,用于对比
            changelog: "1. 优化了界面显示\n2. 修复了已知问题",//更新内容
            path: "xxx.apk"//下载地址
        };
        //获取当前安装包版本号
        const currentVersionCode = await this.getCurrentVersionCode();
        console.log("当前版本号:", currentVersionCode);
        console.log("最新版本号:", Response);
        // 对比版本号
        if (Response.latestVersionCode > currentVersionCode) {
            // 显示更新对话框
            appDialog.show(Response.path, Response.changelog);
        } else {
            uni.showToast({
                title: '当前已是最新版',
                icon: 'none'
            });
        }
    },
    getCurrentVersionCode() {
        return new Promise((resolve) => {
            //获取当前安装包版本号
            plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) => {
                resolve(parseInt(wgtinfo.versionCode));
            });
        });
    }
},

 js_sdk/dialog.js

/**
 * @Descripttion: app升级弹框
 * @Version: 1.0.0
 * @Author: leefine
 */

import config from '@/upgrade-config.js'
import upgrade from './upgrade'

const {
	title = '发现新版本',
		confirmText = '立即更新',
		cancelTtext = '稍后再说',
		confirmBgColor = '#409eff',
		showCancel = true,
		titleAlign = 'left',
		descriAlign = 'left',
		icon
} = config.upgrade;

class AppDialog {
	constructor() {
		this.maskEl = {}
		this.popupEl = {}
		this.screenHeight = 600;
		this.popupHeight = 230;
		this.popupWidth = 300;
		this.viewWidth = 260;
		this.descrTop = 130;
		this.viewPadding = 20;
		this.iconSize = 80;
		this.titleHeight = 30;
		this.textHeight = 18;
		this.textSpace = 10;
		this.popupContent = []
		this.apkUrl = '';
	}

	// 显示
	show(apkUrl, changelog) {
		this.drawView(changelog)
		this.maskEl.show()
		this.popupEl.show()
		this.apkUrl = apkUrl;
	}

	// 隐藏
	hide() {
		this.maskEl.hide()
		this.popupEl.hide()
	}

	// 绘制
	drawView(changelog) {
		this.screenHeight = plus.screen.resolutionHeight;
		this.popupWidth = plus.screen.resolutionWidth * 0.8;
		this.popupHeight = this.viewPadding * 3 + this.iconSize + 100;
		this.viewWidth = this.popupWidth - this.viewPadding * 2;
		this.descrTop = this.viewPadding + this.iconSize + this.titleHeight;
		this.popupContent = [];

		if (icon) {
			this.popupContent.push({
				id: 'logo',
				tag: 'img',
				src: icon,
				position: {
					top: '0px',
					left: (this.popupWidth - this.iconSize) / 2 + 'px',
					width: this.iconSize + 'px',
					height: this.iconSize + 'px'
				}
			});
		} else {
			this.popupContent.push({
				id: 'logo',
				tag: 'img',
				src: '_pic/upgrade.png',
				position: {
					top: '0px',
					left: (this.popupWidth - this.iconSize) / 2 + 'px',
					width: this.iconSize + 'px',
					height: this.iconSize + 'px'
				}
			});
		}

		// 标题
		if (title) {
			this.popupContent.push({
				id: 'title',
				tag: 'font',
				text: title,
				textStyles: {
					size: '18px',
					color: '#333',
					weight: 'bold',
					align: titleAlign
				},
				position: {
					top: this.descrTop - this.titleHeight - this.textSpace + 'px',
					left: this.viewPadding + 'px',
					width: this.viewWidth + 'px',
					height: this.titleHeight + 'px'
				}
			})
		} else {
			this.descrTop -= this.titleHeight;
		}

		this.drawText(changelog)

		// 取消
		if (showCancel) {
			const width = (this.viewWidth - this.viewPadding) / 2;
			const confirmLeft = width + this.viewPadding * 2;
			this.drawBtn('cancel', width, cancelTtext)
			this.drawBtn('confirm', width, confirmText, confirmLeft)
		} else {
			this.drawBtn('confirmBox', this.viewWidth, confirmText)
		}

		this.drawBox(showCancel)
	}

	// 描述内容
	drawText(changelog) {
		if (!changelog) return [];
		const textArr = changelog.split('')
		const len = textArr.length;
		let prevNode = 0;
		let nodeWidth = 0;
		let letterWidth = 0;
		const chineseWidth = 14;
		const otherWidth = 7;
		let rowText = [];

		for (let i = 0; i < len; i++) {
			// 包含中文
			if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) {
				// 包含字母
				let textWidth = ''
				if (letterWidth > 0) {
					textWidth = nodeWidth + chineseWidth + letterWidth * otherWidth;
					letterWidth = 0;
				} else {
					// 不含字母
					textWidth = nodeWidth + chineseWidth;
				}

				if (textWidth > this.viewWidth) {
					rowArrText(i, chineseWidth)
				} else {
					nodeWidth = textWidth;
				}
			} else {
				// 不含中文
				// 包含换行符
				if (/\n/g.test(textArr[i])) {
					rowArrText(i, 0, 1)
					letterWidth = 0;
				} else if (textArr[i] == '\\' && textArr[i + 1] == 'n') {
					rowArrText(i, 0, 2)
					letterWidth = 0;
				} else if (/[a-zA-Z0-9]/g.test(textArr[i])) {
					// 包含字母数字
					letterWidth += 1;
					const textWidth = nodeWidth + letterWidth * otherWidth;
					if (textWidth > this.viewWidth) {
						const preNode = i + 1 - letterWidth;
						rowArrText(preNode, letterWidth * otherWidth)
						letterWidth = 0;
					}
				} else {
					if (nodeWidth + otherWidth > this.viewWidth) {
						rowArrText(i, otherWidth)
					} else {
						nodeWidth += otherWidth;
					}
				}
			}
		}

		if (prevNode < len) {
			rowArrText(len, -1)
		}
		this.drawDesc(rowText)

		function rowArrText(i, nWidth = 0, type = 0) {
			const typeVal = type > 0 ? 'break' : 'text';

			rowText.push({
				type: typeVal,
				content: changelog.substring(prevNode, i)
			})

			if (nWidth >= 0) {
				prevNode = i + type;
				nodeWidth = nWidth;
			}
		}
	}

	// 描述
	drawDesc(rowText) {
		rowText.forEach((item, index) => {
			if (index > 0) {
				this.descrTop += this.textHeight;
				this.popupHeight += this.textHeight;
			}

			this.popupContent.push({
				id: 'content' + index + 1,
				tag: 'font',
				text: item.content,
				textStyles: {
					size: '14px',
					color: '#666',
					align: descriAlign
				},
				position: {
					top: this.descrTop + 'px',
					left: this.viewPadding + 'px',
					width: this.viewWidth + 'px',
					height: this.textHeight + 'px'
				}
			})

			if (item.type == 'break') {
				this.descrTop += this.textSpace;
				this.popupHeight += this.textSpace;
			}
		})
	}

	// 按钮
	drawBtn(id, width, text, left = this.viewPadding) {
		let boxColor = confirmBgColor,
			textColor = '#ffffff';
		if (id == 'cancel') {
			boxColor = '#f0f0f0';
			textColor = '#666666';
		}

		this.popupContent.push({
			id: id + 'Box',
			tag: 'rect',
			rectStyles: {
				radius: '6px',
				color: boxColor
			},
			position: {
				bottom: this.viewPadding + 'px',
				left: left + 'px',
				width: width + 'px',
				height: '40px'
			}
		})

		this.popupContent.push({
			id: id + 'Text',
			tag: 'font',
			text: text,
			textStyles: {
				size: '14px',
				color: textColor
			},
			position: {
				bottom: this.viewPadding + 'px',
				left: left + 'px',
				width: width + 'px',
				height: '40px'
			}
		})
	}

	// 内容框
	drawBox(showCancel) {
		this.maskEl = new plus.nativeObj.View('maskEl', {
			top: '0px',
			left: '0px',
			width: '100%',
			height: '100%',
			backgroundColor: 'rgba(0,0,0,0.5)'
		});

		this.popupEl = new plus.nativeObj.View('popupEl', {
			tag: 'rect',
			top: (this.screenHeight - this.popupHeight) / 2 + 'px',
			left: '10%',
			height: this.popupHeight + 'px',
			width: '80%'
		});

		// 白色背景
		this.popupEl.drawRect({
			color: '#ffffff',
			radius: '8px'
		}, {
			top: this.iconSize / 2 + 'px',
			height: this.popupHeight - this.iconSize / 2 + 'px'
		});

		this.popupEl.draw(this.popupContent);

		this.popupEl.addEventListener('click', e => {
			const maxTop = this.popupHeight - this.viewPadding;
			const maxLeft = this.popupWidth - this.viewPadding;
			const buttonWidth = (this.viewWidth - this.viewPadding) / 2;
			if (e.clientY > maxTop - 40 && e.clientY < maxTop) {
				if (showCancel) {
					// 取消
					// if(e.clientX>this.viewPadding && e.clientX<maxLeft-buttonWidth-this.viewPadding){}
					// 确定
					if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) {
						upgrade.checkOs(this.apkUrl)
					}
				} else {
					if (e.clientX > this.viewPadding && e.clientX < maxLeft) {
						upgrade.checkOs(this.apkUrl)
					}
				}
				this.hide()
			}
		});
	}
}

export default new AppDialog()

js_sdk/upgrade.js

/**
 * @Descripttion: app下载更新
 * @Version: 1.0.0
 * @Author: leefine
 */

import config from '@/upgrade-config.js'
const { upType=0 }=config.upgrade;

class Upgrade{
	
	// 检测平台
	checkOs(apkUrl){
		uni.getSystemInfo({
			success:(res) => {
				if(res.osName=="android"){
					if(upType==1 && packageName){
						plus.runtime.openURL('market://details?id='+packageName)
					}else{
						this.downloadInstallApp(apkUrl)
					}
				}else if(res.osName=='ios' && appleId){
					// apple id 在 app conection 上传的位置可以看到 https://appstoreconnect.apple.com
					plus.runtime.launchApplication({
						action: `itms-apps://itunes.apple.com/cn/app/id${appleId}?mt=8`
					}, function(err) {
						uni.showToast({
							title:err.message,
							icon:'none'
						})
					})
				}
			}  
		})
	}
	
	// 下载更新
	downloadInstallApp(apkUrl){
		const dtask = plus.downloader.createDownload(apkUrl, {}, function (d,status){
			// 下载完成  
			if (status == 200){
				plus.runtime.install(plus.io.convertLocalFileSystemURL(d.filename),{},{},function(error){  
					uni.showToast({  
						title: '安装失败',
						icon:'none'
					});  
				})
			}else{
				uni.showToast({
					title: '更新失败',
					icon:'none'
				});
			}
		});
		this.downloadProgress(dtask);
	}
	
	// 下载进度
	downloadProgress(dtask){
		try{
			dtask.start(); //开启下载任务
			let prg=0;
			let showLoading=plus.nativeUI.showWaiting('正在下载');
			dtask.addEventListener('statechanged',function(task,status){
				// 给下载任务设置监听
				switch(task.state){
					case 1:
						showLoading.setTitle('正在下载');
						break;
					case 2:
						showLoading.setTitle('已连接到服务器');
						break;
					case 3:
						prg=parseInt((parseFloat(task.downloadedSize)/parseFloat(task.totalSize))*100);
						showLoading.setTitle('正在下载'+prg+'%');
						break;
					case 4:
						// 下载完成
						plus.nativeUI.closeWaiting();
						break;
				}
			})
		}catch(e){
			plus.nativeUI.closeWaiting();
			uni.showToast({
				title: '更新失败',
				icon:'none'
			})
		}
	}
	
}

export default new Upgrade()

upgrade-config.js

export default {
    upgrade:{
        packageName:'',
        appleId:'',
        upType:0,
        timer:24,
        icon:'/static/logo.png',
        title:'发现新版本',
        confirmText:'立即更新',
        cancelTtext:'稍后再说',
        confirmBgColor:'#409eff',
        showCancel:true,
        titleAlign:'left',
        descriAlign:'left'
    }
}

效果图:

upgrade.js 中downloadInstallApp函数下载更新代码解析,来自AI:

代码解析

1. plus.downloader.createDownload

这个方法用于创建一个下载任务。它接受三个参数:

  • url: 要下载的文件的 URL 地址。
  • headers: 下载请求的头部信息,通常是一个对象,这里传入的是一个空对象 {}
  • callback: 下载完成后的回调函数,它有两个参数:
    • d: 下载任务对象。
    • status: 下载的状态码,200 表示成功。
2. 回调函数

在下载完成后,回调函数会被调用。根据 status 的值来判断下载是否成功:

  • status == 200: 下载成功,调用 plus.runtime.install 方法安装 APK 文件。
  • status != 200: 下载失败,显示一个更新失败的提示。
3. plus.runtime.install

这个方法用于安装下载好的 APK 文件。它接受四个参数:

  • path: 安装包的路径,这里使用 plus.io.convertLocalFileSystemURL(d.filename) 将下载任务的文件路径转换为本地文件系统路径。
  • options: 安装选项,这里传入的是一个空对象 {}
  • successCallback: 安装成功的回调函数,这里没有具体实现。
  • errorCallback: 安装失败的回调函数,显示一个安装失败的提示。
4. this.downloadProgress(dtask)

这是一个自定义的方法,用于监听下载进度。dtask 是下载任务对象,可以通过这个对象来获取下载的进度信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/906298.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Weblogic漏洞复现(Vulhub)

0x00前言 docker 安装 Docker的安装_docker安装-CSDN博客 docker的镜像 1.可以在阿里云上的容器服务找到镜像源。 2.也可以使用下面的镜像源&#xff0c;时快时慢不稳定。 {"registry-mirrors":["https://docker.registry.cyou","https://docke…

Nico,从零开始干掉Appium,移动端自动化测试框架实现

开头先让我碎碎念一波~去年差不多时间发布了一篇《 UiAutomator Nico&#xff0c;一个基于纯 adb 命令实现的安卓自动化测试框》&#xff08;https://testerhome.com/topics/37042&#xff09;&#xff0c; 由于种种原因 (详见此篇帖子) 当时选择了用纯 adb 命令来实现安卓自动…

音视频入门基础:FLV专题(22)——FFmpeg源码中,获取FLV文件音频信息的实现(中)

本文接着《音视频入门基础&#xff1a;FLV专题&#xff08;21&#xff09;——FFmpeg源码中&#xff0c;获取FLV文件音频信息的实现&#xff08;上&#xff09;》&#xff0c;继续讲解FFmpeg获取FLV文件的音频信息到底是从哪个地方获取的。本文的一级标题从“四”开始。 四、音…

scala 权限

一.访问权限 idea实例 关于protected:

flex 布局比较容易犯的错误 出现边界超出的预想的情况

flex 布局比较容易犯的错误 出现边界超出的预想的情况 如图 当使用flex布局时&#xff0c;设置flex:1 或者是flex:x 时 如果没有多层嵌套的flex布局&#xff0c;内容超出flex&#xff1a;1规定的后&#xff0c;仍然会撑大融器 在flex:1 处设置 overflow:hidden 即可超出后不显…

【vue项目中添加告警音频提示音】

一、前提&#xff1a; 由于浏览器限制不能自动触发音频文件播放&#xff0c;所以实现此类功能时&#xff0c;需要添加触发事件&#xff0c;举例如下&#xff1a; 1、页面添加打开告警声音开关按钮 2、首次进入页面时添加交互弹窗提示&#xff1a;是否允许播放音频 以上两种方…

Java 用户随机选择导入ZIP文件,解压内部word模板并入库,Windows/可视化Linux系统某麒麟国防系统...均可适配

1.效果 压缩包内部文件 2.依赖 <!--支持Zip--><dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>2.11.5</version></dependency>总之是要File类变MultipartFile类型的 好像是…

反悔贪心

Problem - C - Codeforces&#xff08;初识反悔贪心&#xff09; 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; #include <bits/stdc.h> #define fi first #define se secondusing namespace std; typedef pair<int,int> PII;string a, b, ans; bool vis…

Cisco Packet Tracer 8.0 路由器静态路由配置

文章目录 静态路由简介一、定义与特点二、配置与命令三、优点与缺点四、应用场景 一&#xff0c;搭建拓扑图二&#xff0c;配置pc IP地址三&#xff0c;pc0 ping pc1 timeout四&#xff0c;配置路由器Router0五&#xff0c;配置路由器Router1六&#xff0c;测试 静态路由简介 …

burp靶场-Remote code execution via web shell upload

Lab: 通过 Web shell 上传远程执行代码 This lab contains a vulnerable image upload function. It doesn’t perform any validation on the files users upload before storing them on the server’s filesystem. 此实验室包含易受攻击的映像上传功能。在将用户上传的文件…

极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果

目录 前言 1.弹性放大按钮效果 2.旋转和缩放组合动画 3.颜色渐变背景动画 4.缩放进出效果 前言 在上一篇文章中&#xff0c;我们介绍了Flutter中的隐式动画的一些相关知识&#xff0c;在这篇文章中,我们可以结合多个隐式动画 Widget 在 Flutter 中创建一些酷炫的视觉效果&…

后端:Spring-1

文章目录 1. 了解 spring(Spring Framework)2. 基于maven搭建Spring框架2.1 纯xml配置方式来实现Spring2.2 注解方式来实现Spring3. Java Config类来实现Spring 2.4 总结 1. 了解 spring(Spring Framework) 传统方式构建spring(指的是Spring Framework)项目&#xff0c;导入依…

qt QStackedLayout详解

QStackedLayout类提供了一种布局方式&#xff0c;使得在同一时间内只有一个子部件&#xff08;或称为页面&#xff09;是可见的。这些子部件被维护在一个堆栈中&#xff0c;用户可以通过切换来显示不同的子部件&#xff0c;适合用在需要动态显示不同界面的场景&#xff0c;如向…

C++进阶:C++11的新特性

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ C11的发展历史 2011年&#xff0c;C标准委员会发布了C11标准&#xff0c;这是C的一次巨大飞跃&#xff0c;引入了许多重要的新特性&#xff0c;如智能指针、lambda表达式、并发编程支持等。这一版本的发布对C社…

GA/T1400视图库平台EasyCVR视频分析设备平台微信H5小程序:智能视频监控的新篇章

GA/T1400视图库平台EasyCVR是一款综合性的视频管理工具&#xff0c;它兼容Windows、Linux&#xff08;包括CentOS和Ubuntu&#xff09;以及国产操作系统。这个平台不仅能够接入多种协议&#xff0c;还能将不同格式的视频数据统一转换为标准化的视频流&#xff0c;通过无需插件的…

OpenAI推出搜索GPT,进军搜索引擎领域

OpenAI 推出了一项新功能——Search GPT&#xff0c;为 ChatGPT 引入实时网络搜索功能&#xff0c;使其站上与 Google 和 Bing 等搜索巨头竞争的舞台。 OpenAI 产品的重大变化&#xff0c;Search GPT 承诺提供快捷、实时的答案&#xff0c;并附上可靠来源的链接。 ChatGPT 一直…

Unity XR Interaction Toolkit 开发教程(3)快速配置交互:移动、抓取、UI交互【3.0以上版本】

获取完整课程以及答疑&#xff0c;工程文件下载&#xff1a; https://www.spatialxr.tech/ 视频试看链接&#xff1a; 3.快速配置交互&#xff1a;移动、抓取、UI交互【Unity XR Interaction Toolkit 跨平台开发教程】&#xff08;3.0以上版本&#xff09; 系列教程专栏&…

SE-Net模型实现猴痘病识别

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【DeepLabV3模型实现人体部位分割CIHP数据】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.…

深度学习之权重、偏差

1 权重偏差初始化 1.1 全都初始化为 0 偏差初始化陷阱&#xff1a; 都初始化为 0。 产生陷阱原因&#xff1a;因为并不知道在训练神经网络中每一个权重最后的值&#xff0c;但是如果进行了恰当的数据归一化后&#xff0c;我们可以有理由认为有一半的权重是正的&#xff0c;另…

企业物流管理数据仓库建设的全面指南

文章目录 一、物流管理目标二、总体要求三、数据分层和数据构成&#xff08;1&#xff09;数据分层&#xff08;2&#xff09;数据构成 四、数据存储五、数据建模和数据模型&#xff08;1&#xff09;数据建模&#xff08;2&#xff09;数据模型 六、总结 在企业物流管理中&…