Uniapp 实现app自动检测更新/自动更新功能
实现步骤
- 配置 manifest.json
- 在
manifest.json
中设置应用的基本信息,包括versionName
和versionCode
。
- 在
一般默认0.0.1,1.
- 服务器端接口开发
- 提供一个 API 接口,返回应用的最新版本信息,版本号、下载链接。
- 客户端检测更新
- 使用
uni.request
发送请求到服务器端接口,获取最新版本信息。 - 对比本地版本与服务器版本,判断是否需要更新。
- 使用
- 展示更新提示
- 如果需要更新,使用
uni.showModal
方法展示更新提示。
- 如果需要更新,使用
- 处理用户选择
- 用户选择更新后,调用
plus.downloader.createDownload
方法下载新版本。 - 监听下载进度,并在下载完成后调用
plus.runtime.install
安装新版本。
- 用户选择更新后,调用
- 异常处理
- 对可能出现的错误进行捕获和处理,确保良好的用户体验。
我是参考的一个插件把过程简化了一些
插件地址: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.complus.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
是下载任务对象,可以通过这个对象来获取下载的进度信息。
相关文章:

Uniapp 实现app自动检测更新/自动更新功能
实现步骤 配置 manifest.json 在 manifest.json 中设置应用的基本信息,包括 versionName 和 versionCode。 一般默认0.0.1,1. 服务器端接口开发 提供一个 API 接口,返回应用的最新版本信息,版本号、下载链接。客户端检测更新 使…...

7.0、RIP
RIP (Routing Information Protocol) 简介 RIP是由Xerox在20世纪70年代开发的,最初定义在RFC1058中。RIP用两种数据包传输更新:更新和请求,每个有RIP功能的路由器在默认情况下,每隔30s利用UDP520端口向与它直连的网络邻居广播(RIP1)或组播(R…...

C#与C++结构体的交互
C#在和C进行交互时,有时候会需要传递结构体。 做一些总结,避免大家在用的时候踩坑。 一般情况 例如我们在C里定义了一个struct_basic结构体 1 struct struct_basic 2 { 3 WORD value_1; 4 LONG value_2; 5 DWORD value_3; 6 UINT v…...
sql纵表转横表
项目上有一个需求(例子): 用户表 user{ id, name, workCode } id name workCode 1 张三 WC1001 2 李四 WC1002 工作信息表 work{ id, name, workCode, workTimeSun } id name …...

数据采集-Kepware OPCUA 服务器实现
KepserverEX OPC UA server设置 系列文章目录 数据采集-Kepware 安装证书异常处理 目录 KepserverEX OPC UA server设置系列文章目录一、OPC UA(OPC Unified Architecture)二、防火墙的配置三、配置KepserverEX的OPC UA3.1 启用远程连接3.2 启动OPCUA服务器接口 四、管理OPCU…...

初识计算机网络
🌎初识计算机网络 文章目录: 初识计算机网络 计算机网络背景 网络协议 初识协议 制定协议标准的组织或公司 OSI七层模型 操作系统和计算机网络关系 再谈协议 网络传输的基本流程 …...
Oracle 第11章:异常处理
在 Oracle PL/SQL 中,异常处理是一个重要的概念,它用于管理程序执行过程中可能发生的错误或特殊情况。异常可以是系统预定义的,也可以是由用户自定义的。 异常类型与处理机制 PL/SQL 提供了两种类型的异常: 预定义异常…...

导航栏渐变色iOS
- (void)viewDidLoad {[super viewDidLoad];// 设置导航栏属性self.navigationBar.translucent NO;[self.navigationBar setTitleTextAttributes:{NSForegroundColorAttributeName : [UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:28]}];// 修复iO…...

mysql读写分离
一、proxysql实现mysql读写分离 二、mycat...

计算机的错误计算(一百四十二)
摘要 本节探讨 MATLAB中 附近数的正弦函数的计算精度问题。 例1. 已知 计算 与 直接贴图吧: 另外, 16位的正确值分别为 -0.3077518861551721e-8 与 0.4106402475009074e-3(ISRealsoft 提供)。 容易看出,MATLAB的…...

利用大模型辅助科研论文写作·第一期|论文写作·24-11-02
小罗碎碎念 从这期推文开始,开一个新的系列——如何利用大语言模型辅助论文写作。 我目前的推文主要都集中于分享已经发表的论文,前期背景积累到一定程度以后,我们要动手做实验然后写自己的论文。如果从头到尾,全都自己写…...

JavaScript。—关于语法基础的理解—
一、程序控制语句 JavaScript 提供了 if 、if else 和 switch 3种条件语句,条件语句也可以嵌套。 (一)、条件语句 1、单向判断 : if... (1)概述 < if >元素用于在判断该语句是否满足特定条…...

Tomcat 11 下载/安装 与基本使用
为什么要使用Tomcat? 使用Apache Tomcat的原因有很多,以下是一些主要的优点和特点: 1. 开源与免费 Tomcat是一个完全开源的项目,任何人都可以免费使用。它由Apache软件基金会维护,拥有一个活跃的社区,这…...
Linux系统时间服务——Chrony服务器
文章目录 Linux系统时间服务——Chrony服务器前言时间同步的重要性Linux系统的两种时钟系统时钟(System Clock)相关命令硬件时钟 (RTC - Real Time Clock)相关命令 Chrony介绍NTP Chronyc相关命令服务管理相关命令chronyc 基本命令时间校正和控制命令NTP…...
C# 接口(Interface)
C# 接口(Interface) 接口在C#中是一种非常重要的概念,它定义了一个约定,实现该接口的类必须遵循这个约定。接口可以包含方法、属性、事件和索引器,但不包含实现。这使得接口成为定义抽象行为的理想选择。在本文中&…...

《高频电子线路》—— 电容三端LC振荡器
文章内容来源于【中国大学MOOC 华中科技大学通信(高频)电子线路精品公开课】,此篇文章仅作为笔记分享。 电容三端LC振荡器 基本原理(考毕兹电路) 反馈电压从C2上取得,作为输入电压,形成正反馈&a…...

leetcode35.搜索插入位置
1)题目描述: 2)本题要求使用 时间复杂度O(log n)的算法,这里使用二分查找的方法,这道题本身不复杂,但是,在使用递归调用时,笔者经常把递归结束的边界搞错,这里给出几版代…...

Redis全系列学习基础篇之位图(bitmap)常用命令的解析
文章目录 描述常用命令及解析常用命令解析 应用场景统计不确定时间周期内用户登录情况思路分析实现 统计某一特定时间内活跃用户(登录一次即算活跃)的数量思路分析与实现 描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计…...

Copilot功能
Copilot 1、简介:Copilot是由GitHub与OpenAI共同开发的一款AI编程助手,旨在帮助开发者提高工作效率,改善代码质量。 2、主要功能包括: 1.代码补全:Copilot可以在开发者编写代码时提供代码建议,包括函数、循…...
《GBDT 算法的原理推导》 11-13初始化模型 公式解析
本文是将文章《GBDT 算法的原理推导》中的公式单独拿出来做一个详细的解析,便于初学者更好的理解。 公式(11-13)是GBDT算法的第一步,它描述了如何初始化模型。公式如下: f 0 ( x ) arg min c ∑ i 1 N L ( y i , c ) f_0(x) \arg \m…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...