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.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 是下载任务对象,可以通过这个对象来获取下载的进度信息。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

java访问华为网管软件iMaster NCE的北向接口

最近做的一个项目&#xff0c;需要读取华为一个叫iMaster NCE的网管软件的北向接口。这个iMaster NCE&#xff08;以下简称NCE&#xff09;用于管理项目的整个网络&#xff0c;尤其是光网络。业主要求我们访问该软件提供的对外接口&#xff0c;读取一些网络信息&#xff0c;比如…

docker基础篇(尚硅谷)

学习链接 docker1️⃣基础篇&#xff08;零基小白&#xff09; - 语雀文档 (即本篇) Docker与微服务实战&#xff08;基础篇&#xff09; Docker与微服务实战&#xff08;高级篇&#xff09;- 【上】 Docker与微服务实战&#xff08;高级篇&#xff09;- 【下】 文章目录 学习…

华为通过FTP进行文件操作示例

小知学网络-CSDN博客 目录 通过FTP进行文件操作简介 配置注意事项 组网需求 配置思路 操作步骤 配置文件 组网图形 图1 通过FTP进行文件操作组网图 通过FTP进行文件操作简介配置注意事项组网需求配置思路操作步骤配置文件相关信息 通过FTP进行文件操作简介 配置设备作…

嵌入式开发教程之Linux下IO流

一、文件的概念和类型 文件基础&#xff1a; 概念&#xff1a;一组相关数据的有序集合&#xff0c;文件名、路径。通过文件名指定访问什么文件。 文件类型&#xff1a; 常规文件 r&#xff0c;分为&#xff1a;普通文件&#xff0c;文本文件&#xff08;可见字符&#xff09…

Rust 力扣 - 48. 旋转图像

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们可以将原矩阵进行水平翻转&#xff0c;然后在沿主对角线进行翻转&#xff0c;就能完成原矩阵沿顺时针方向旋转90o的变换 题解代码 impl Solution {pub fn rotate(matrix: &mut Vec<Vec<i32>&…

MySQL 8.0在windows环境安装及配置

文章目录 一、下载二、安装三、配置环境变量 一、下载 1、先彻底卸载之前的MySQL&#xff0c;并清理其 残留文件 。 2、登录网址https://www.mysql.com/ 3、点击网址左下角“中文”按钮&#xff0c;切换到中文界面 4、点击网页上方的“下载”按钮&#xff0c;然后点击网页…

Sleep_Monitor 2.7.9.2 | 您的个人睡眠助手,帮助您睡得更好

晚上入睡困难吗&#xff1f;Sleep Monitor 是您的睡眠专家&#xff01;这款应用程序可以追踪并记录您的睡眠周期&#xff0c;让您早晨醒来时精神焕发&#xff0c;准备好迎接新的一天。Sleep Monitor 是一个功能丰富的程序&#xff0c;让睡眠变得更加愉快。通过追踪您的打鼾、梦…

基于SSM+微信小程序的汽车维修管理系统(汽车5)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的汽车维修管理系统实现了三个角色&#xff1a;管理员、员工、用户。 1、管理员实现了首页、管理员管理员、员工管理、用户管理、车辆信息管理、配件管理等 2、员工实…

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 文章目录 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC1. 介绍 二. 参考文献 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 如何在 Ubuntu 22.04 上安装和配置 VNChttps://hub.docker.c…

jvm学习笔记-轻量级锁内存模型

一&#xff0c;轻量级锁 LockRecord的那个第一个成员变量是拷贝对应锁定了的java对象资源的MarkWord&#xff0c;Lock Record有一个Ptr指针刚开始指向自己&#xff0c;后面这个指针存储在锁定资源的java对象的markword中&#xff0c;后续可以通过java对象的MarkWord快速定位到…

在Windows 10上安装Tesseract并用pytesseract运行OCR任务

诸神缄默不语-个人CSDN博文目录 文章目录 1. Tesseract安装2. pytesseract的安装与使用3. 手动安装其他语种并在pytesseract中调用4. 本文撰写过程中参考的其他网络资料 1. Tesseract安装 Tesseract官方GitHub项目链接&#xff1a;https://github.com/tesseract-ocr/tesseract…

golang的多表联合orm

项目截图 1.数据库连接配置 DbConfigUtil.go package configimport ( "fmt" _ "github.com/go-sql-driver/mysql" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" )var Go…

ONLYOFFICE 8.2版本桌面编辑器评测

目录 ONLYOFFICE 8.2版本桌面编辑器评测一、引言二、ONLYOFFICE 桌面编辑器概述2.1 功能特点2.2 系统支持2.3 数据表&#xff1a;ONLYOFFICE 桌面编辑器功能概述 三、ONLYOFFICE 协作空间3.1 协作功能3.2 部署和集成3.3 数据表&#xff1a;ONLYOFFICE 协作空间功能概述 四、ONL…

图片懒加载(自定义指令)

----------------------------------------------------------- 图片懒加载自定义指令使用mock模拟随机图片列表组件如下&#xff08;主要内容&#xff09;&#xff1a;配置自定义指令 图片懒加载 实现思路 使用自定义指令实现通用图片懒加载&#xff08;在图片到达视口内时再…

三层交换技术,eNSP实验讲解

三层交换技术&#xff0c;eNSP实验讲解 一、简要介绍1、概念2、工作原理3、优点4、应用场景5、与路由器的区别 二、eNSP仿真实验1、步骤一&#xff1a;创建连接&#xff0c;明确参数。2、步骤二&#xff1a;设置PC1和PC2参数3、步骤三&#xff1a;配置交换机&#xff0c;通过命…

扫雷(C语言)

目录​​​​​​​ 前言 一、前提知识 二、扫雷游戏编写 2.2 test文件基本逻辑 2.2.1菜单编写 2.2.2game函数的逻辑 2.2.2.1定义两个数组 2.2.2.2两个数组数组的初始化 2.2.2.3打印棋盘 2.2.2.4布置雷 2.2.2.5排查雷 2.2.2.6获取坐标附近雷的数量 2.2.2.7什么时候…

Node.js:模块 包

Node.js&#xff1a;模块 & 包 模块module对象 包npm安装包配置文件镜像源 分类 模块 模块化是指解决一个复杂问题时&#xff0c;自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说&#xff0c;模块是可组合、分解和更换的单元。 简单来说&#xff0c;就是把一个…

恋爱脑学Rust之dyn关键字的作用

在 Rust 语言中&#xff0c;dyn 关键字允许我们在使用特征时创建“动态派发”——即通过一个统一的接口操作多种类型的具体实现。可以把它理解成一种“浪漫的妥协”&#xff1a;当我们不知道未来会爱上谁&#xff0c;只知道对方一定具有某种特征时&#xff0c;dyn 就像一个协议…

vue3项目中实现el-table分批渲染表格

开篇 因最近工作中遇到了无分页情景下页面因大数据量卡顿的问题&#xff0c;在分别考虑并尝试了懒加载、虚拟滚动、分批渲染等各个方法后&#xff0c;最后决定使用分批渲染来解决该问题。 代码实现 表格代码 <el-table :data"currTableData"borderstyle"wi…

Qt自定义控件:汽车速度表

1、功能 制作一个汽车速度表 2、实现 从外到内进行绘制&#xff0c;初始化画布&#xff0c;画渐变色外圈&#xff0c;画刻度&#xff0c;写刻度文字&#xff0c;画指针&#xff0c;画扇形&#xff0c;画内圈渐变色&#xff0c;画黑色内圈&#xff0c;写当前值 3、效果 4、源…