uniapp APP端在线升级功能实现讲解——强制或可选升级,下载进度显示

news2024/11/17 8:38:48

文章目录

概要

需求分析

  

技术实现梳理

 1.是否更新判断:

 2.升级弹窗的展示

 3.根据升级类型限制操作

 4.下载APP监听下载进度

 5.下载完自动安装

核心API讲解

 1.plus.downloader.createDownload(url,options,completedCallback)(下载)

 2.plus.runtime.install(安装APP)

        代码实现

     效果演示

        注意事项


概要

本文主要讲述uniapp APP在线升级功能实现,并用代码演示包括强制升级、可选升级、下载进度显示、下载自动安装等功能,示例代码已经过测试可结合实际开发场景做调整直接引入使用

需求分析

要求:

1.打开APP自动检测是否有最新版本,如有弹窗提示下载更新

2.升级类型分为可选更新,强制更新,可选更新用户可以选择关闭不更新情况下继续使用APP,强制更新用户无法关闭更新窗口,无法使用任何功能,必须在线升级后才能使用

3.下载过程进度条显示下载进度

4.下载完成自动跳转安装界面,用户取消安装还能继续手动点击安装

  

技术实现梳理

    1.是否更新判断:

        通过接口获取线上最新版本号(默认规定版本号为正整数)与本地APP版本号进行比较大小,当线上最新版本号大于本地版本号就需要更新。本地App版本可在每次发版时候在manifest.json-基础配置-应用版本号进行设置

   2.升级弹窗的展示

       升级弹窗实现有2种方案,一种直接在首页里嵌套弹窗组件,另一种是把弹窗放置在独立的页面,并把页面窗口设置透明,当需要升级的时候直接从首页进入,从视觉效果上看就相当于在首页上的悬浮窗口。考虑到后续有强制更新页面不能返回等操作,便于维护本案例将采用第二种方案

 3.根据升级类型限制操作

   当升级类型为强制升级意味着页面不能做除了升级的任何操作,包括返回功能,关闭弹窗功能,禁止返回可通过onBackPress生命周期函数处理,弹窗关闭入口动态控制,包括关闭按钮,遮罩层点击关闭功能等

 4.下载APP监听下载进度

  通过H5+方式下载 :plus.downloader.createDownload,生成下载任务对象(downloadTask),通过downloadTask.addEventListener("statechanged",(task,status)=>{})监听下载进度

 5.下载完自动安装

  通过H5+ plus.runtime.install实现自动安装,该api只能监听是否打开安装页面,无法监测到apk是否安装成功,还需要调用安卓原生注册广播事件,监听apk安装成功回调

核心API讲解

  1.plus.downloader.createDownload(url,options,completedCallback)(下载)

     说明:

        请求下载管理创建新的下载任务,创建成功则返回Download对象,用于管理下载任务   

     参数:

  • url: ( String ) 必选 要下载文件资源地址

    要下载文件的url地址,仅支持网络资源地址,支持http或https协议。 允许创建多个相同url地址的下载任务。 注意:如果url地址中包含中文或空格等,需要进行urlencode转换。

  • options: DownloadOptions ) 可选 下载任务的参数

    可通过此参数设置下载任务属性,如保存文件路径、下载优先级等。

  • completedCallback: DownloadCompletedCallback ) 可选 下载任务完成回调函数

    当下载任务下载完成时触发,成功或失败都会触发。

    返回值:

     Download:新建的下载任务对象

 2.plus.runtime.install(安装APP)

    说明:

    支持以下类型安装包: 1. 应用资源安装包(wgt),扩展名为'.wgt'; 2. 应用资源差量升级包(wgtu),扩展名为'.wgtu'; 3. 系统程序安装包(apk),要求使用当前平台支持的安装包格式。       注意:仅支持本地地址,调用此方法前需把安装包从网络地址或其他位置放置到运行时环境可以访问的本地目录。

   参数:

  • filePath: ( String ) 必选 要安装的文件路径

    支持应用资源安装包(wgt)、应用资源差量升级包(wgtu)、系统程序包(apk)。

  • options: ( WidgetOptions ) 可选

    应用安装设置的参数

  • installSuccessCB: ( InstallSuccessCallback ) 可选

    正确安装后的回调

  • installErrorCB: ( InstallErrorCallback ) 可选

    安装失败的回调

  返回值:

     void : 无

代码实现

    项目目录:

     

 pages.json:

    新增升级弹窗页面路由,设置窗口透明

  {
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/index/index",//首页
			"style": {
				"navigationBarTitleText": ""
			}
		},
		{
			"path": "pages/index/upgrade",//升级窗口页面
			"style": {
				"navigationBarTitleText": "",
				"navigationStyle": "custom",//导航栏自定义
				"app-plus": {
					"bounce": "none",
                    "animationType":"none",//取消窗口动画
					"background": "transparent" // 设置背景透明
				}
			}
		}
	],
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "uni-app",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {}
}

index.vue:(首页)

  从接口获取最新版本号,跟本地对比,判断是否进入升级弹窗页

<script>
	export default {
		data() {
			return {}
		},
		onLoad() {
			this.init()

		},
		methods: {
			//初始化
			init() {
				// #ifdef APP-PLUS
				this.checkVersion()
				// #endif
			},
			//检查版本更新情况
			checkVersion() {
				//模拟接口获取最新版本号,版本号固定为整数
				setTimeout(() => {
					const newVersionName = "V1.2.0" //线上最新版本名
					const newVersionCode = 20; //线上最新版本号
					const selfVersionCode = Number(uni.getSystemInfoSync().appVersionCode) //当前App版本号

					//线上版本号高于当前,进行在线升级
					if (selfVersionCode < newVersionCode) {
						let platform = uni.getSystemInfoSync().platform //手机平台
						//安卓手机弹窗升级
						if (platform === 'android') {
							uni.navigateTo({
								url: './upgrade'
							})
						}
						//IOS无法在线升级提示到商店下载
						else {
							uni.showModal({
								title: '发现新版本 ' + newVersionName,
								content: '请到App store进行升级',
								showCancel: false
							})
						}
					}


				}, 200)
			}
		}
	}
</script>

upgrade.vue(升级弹窗页):

布局样式可根据实际调整,顶部背景图upgrade_bg.png自行放入static

<template>
	<view class="upgrade-popup">
		<image class="header-bg" src="../../static/upgrade_bg.png" mode="widthFix"></image>
		<view class="main">
			<view class="version">发现新版本{{versionName}}</view>
			<view class="content">
				<text class="title">更新内容</text>
				<view class="desc" v-html="versionDesc"></view>
			</view>
			<!--下载状态-进度条显示 -->
			<view class="footer" v-if="isStartDownload">
				<view class="progress-view" :class="{'active':!hasProgress}" @click="handleInstallApp">
					<!-- 进度条 -->
					<view v-if="hasProgress" style="height: 100%;">
						<view class="txt">{{percentText}}</view>
						<view class="progress" :style="setProStyle"></view>
					</view>
					<view v-else>
						<view class="btn upgrade force">{{ isDownloadFinish  ? '立即安装' :'下载中...'}}</view>
					</view>
				</view>
			</view>
			<!-- 强制更新 -->
			<view class="footer" v-else-if="isForceUpdate">
				<view class="btn upgrade force" @click="handleUpgrade">立即更新</view>
			</view>
			<!-- 可选择更新 -->
			<view class="footer" v-else>
				<view class="btn close" @click="handleClose">以后再说</view>
				<view class="btn upgrade" @click="handleUpgrade">立即更新</view>
			</view>
		</view>
	</view>
</template>

<script>
	import {
		downloadApp,
		installApp
	} from './upgrade.js'
	export default {
		data() {
			return {
				isForceUpdate: false, //是否强制更新
				versionName: '', //版本名称
				versionDesc: '', //更新说明
				downloadUrl: '', //APP下载链接
				isDownloadFinish: false, //是否下载完成
				hasProgress: false, //是否能显示进度条
				currentPercent: 0, //当前下载百分比
				isStartDownload: false, //是否开始下载
				fileName: '', //下载后app本地路径名称
			}
		},
		computed: {
			//设置进度条样式,实时更新进度位置
			setProStyle() {
				return {
					width: (510 * this.currentPercent / 100) + 'rpx' //510:按钮进度条宽度
				}
			},
			//百分比文字
			percentText() {
				let percent = this.currentPercent;
				if (typeof percent !== 'number' || isNaN(percent)) return '下载中...'
				if (percent < 100) return `下载中${percent}%`
				return '立即安装'

			}
		},
		onLoad() {
			this.init()
		},
		onBackPress(options) {
			// 禁用返回
			if (options.from == 'backbutton') {
				return true;
			}

		},
		methods: {
			//初始化获取最新APP版本信息
			init() {
				//模拟接口获取
				setTimeout(() => {
                   //演示数据请根据实际修改
					this.versionName = 'V1.2.0'; //版本名称
					this.versionDesc = "修复若干bug"; //更新说明
					this.downloadUrl = 'https://xxxxx'; //下载链接
					this.isForceUpdate = false; //是否强制更新
				}, 200)
			},
			//更新
			handleUpgrade() {
				if (this.downloadUrl) {
					this.isStartDownload = true
					//开始下载App
					downloadApp(this.downloadUrl, current => {
						//下载进度监听
						this.hasProgress = true
						this.currentPercent = current

					}).then(fileName => {
						//下载完成
						this.isDownloadFinish = true
						this.fileName = fileName
						if (fileName) {
							//自动安装App
							this.handleInstallApp()
						}
					}).catch(e => {
						console.log(e, 'e')
					})
				} else {
					uni.showToast({
						title: '下载链接不存在',
						icon: 'none'
					})
				}

			},
			//安装app
			handleInstallApp() {
				//下载完成才能安装,防止下载过程中点击
				if (this.isDownloadFinish && this.fileName) {
					installApp(this.fileName, () => {
						//安装成功,关闭升级弹窗
						uni.navigateBack()
					})
				}
			},
			//关闭返回
			handleClose() {
				uni.navigateBack()
			},
		}
	}
</script>

<style>
	page {
		background: rgba(0, 0, 0, 0.5);/**设置窗口背景半透明*/
	}
</style>
<style lang="scss" scoped>
	.upgrade-popup {
		width: 580rpx;
		height: auto;
		position: fixed;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		background: #fff;
		border-radius: 20rpx;
		box-sizing: border-box;
		border: 1px solid #eee;
	}

	.header-bg {
		width: 100%;
		margin-top: -112rpx;
	}

	.main {
		padding: 10rpx 30rpx 30rpx;
		box-sizing: border-box;
		.version {
			font-size: 36rpx;
			color: #026DF7;
			font-weight: 700;
			width: 100%;
			text-align: center;
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
			letter-spacing: 1px;
		}

		.content {
			margin-top: 60rpx;

			.title {
				font-size: 28rpx;
				font-weight: 700;
				color: #000000;
			}

			.desc {
				box-sizing: border-box;
				margin-top: 20rpx;
				font-size: 28rpx;
				color: #6A6A6A;
				max-height: 80vh;
				overflow-y: auto;
			}
		}

		.footer {
			width: 100%;
			display: flex;
			justify-content: center;
			align-items: center;
			position: relative;
			flex-shrink: 0;
			margin-top: 100rpx;

			.btn {
				width: 246rpx;
				display: flex;
				justify-content: center;
				align-items: center;
				position: relative;
				z-index: 999;
				height: 96rpx;
				box-sizing: border-box;
				font-size: 32rpx;
				border-radius: 10rpx;
				letter-spacing: 2rpx;

				&.force {
					width: 500rpx;
				}

				&.close {
					border: 1px solid #E0E0E0;
					margin-right: 25rpx;
					color: #000;
				}

				&.upgrade {
					background-color: #026DF7;
					color: white;
				}
			}

			.progress-view {
				width: 510rpx;
				height: 90rpx;
				display: flex;
				position: relative;
				align-items: center;
				border-radius: 6rpx;
				background-color: #dcdcdc;
				display: flex;
				justify-content: flex-start;
				padding: 0px;
				box-sizing: border-box;
				border: none;
				overflow: hidden;

				&.active {
					background-color: #026DF7;
				}

				.progress {
					height: 100%;
					background-color: #026DF7;
					padding: 0px;
					box-sizing: border-box;
					border: none;
					border-top-left-radius: 10rpx;
					border-bottom-left-radius: 10rpx;

				}

				.txt {
					font-size: 28rpx;
					position: absolute;
					top: 50%;
					left: 50%;
					transform: translate(-50%, -50%);
					color: #fff;
				}
			}
		}
	}
</style>

upgrade.js(下载、安装工具类):

/**
 * @description H5+下载App
 * @param downloadUrl:App下载链接
 * @param progressCallBack:下载进度回调
 */
export const downloadApp = (downloadUrl, progressCallBack = () => {}, ) => {
	return new Promise((resolve, reject) => {
		//创建下载任务
		const downloadTask = plus.downloader.createDownload(downloadUrl, {
			method: "GET"
		}, (task, status) => {
			console.log(status,'status')
			if (status == 200) { //下载成功
				resolve(task.filename)

			} else {
				reject('fail')
				uni.showToast({
					title: '下载失败',
					duration: 1500,
					icon: "none"
				});
			}
		})
		//监听下载过程
		downloadTask.addEventListener("statechanged", (task, status) => {
			switch (task.state) {
				case 1: // 开始  
					break;
				case 2: //已连接到服务器  
					break;
				case 3: // 已接收到数据  
					let hasProgress = task.totalSize && task.totalSize > 0 //是否能获取到App大小
					if (hasProgress) {
						let current = parseInt(100 * task.downloadedSize / task.totalSize); //获取下载进度百分比
						progressCallBack(current)
					}
					break;
				case 4: // 下载完成       
					break;
			}
		});
		//开始执行下载
		downloadTask.start();
	})


}
/**
 * @description H5+安装APP
 * @param fileName:app文件名
 * @param callBack:安装成功回调
 */
export const installApp = (fileName, callBack = () => {}) => {
	//注册广播监听app安装情况
	onInstallListening(callBack);
	//开始安装
	plus.runtime.install(plus.io.convertLocalFileSystemURL(fileName), {}, () => {
		//成功跳转到安装界面
	}, function(error) {
		uni.showToast({
			title: '安装失败',
			duration: 1500,
			icon: "none"
		});
	})

}
/**
 * @description 注册广播监听APP是否安装成功
 * @param callBack:安装成功回调函数
 */
const onInstallListening = (callBack = () => {}) => {

	let mainActivity = plus.android.runtimeMainActivity(); //获取activity
	//生成广播接收器
	let receiver = plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
		onReceive: (context, intent) => { //接收广播回调  
			plus.android.importClass(intent);
			mainActivity.unregisterReceiver(receiver); //取消监听
			callBack()
		}
	});
	let IntentFilter = plus.android.importClass('android.content.IntentFilter');
	let Intent = plus.android.importClass('android.content.Intent');
	let filter = new IntentFilter();
	filter.addAction(Intent.ACTION_PACKAGE_ADDED); //监听APP安装     
	filter.addDataScheme("package");
	mainActivity.registerReceiver(receiver, filter); //注册广播

}

效果演示

注意事项

      1.无法弹出安装APP界面 

          manifest.json-APP常用其他设置-targetSdkVersion必须设置26以上

     2.无法安装APP

         manifest.json-APP权限设置需勾选:

        "<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>",
       "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>"

    3.获取的版本号和设置的不一致

        通过uni.getSystemInfoSync().appVersionCode获取的本地应用版本号和manifest.json-应用版本号设置不一致,需要云打包或者自定义基座里面才能生效

    4.无法获取下载进度

      app下载请求回复体头部需要返回content-length字段,才能正常获取到app总大小,需要下载接口开启支持,本演示例子已做显示的兼容处理。

 

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

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

相关文章

使用SOCKET搭建linux和window实现实时摄像头传输(linux传输win端使用C++mfc显示)--Win端开发

1.使用MFC搭建框架 配置: Window10VS2013opencv249 如果VS和opencv配置不一样&#xff0c;让版本对应 Opencv与VS版本 1.1 MFC项目搭建 通过这些步骤就创建了一个MFC基础项目。 1.2项目属性配置 本项目因为要使用opencv,所以就要配置以下opencv的环境 首先在opencv官网下载…

手机连接adb 相关问题汇总

目录 关于端口占用问题1 关于修改adb 端口配置问题2 方法3 方法4 关于端口占用问题1 转载链接&#xff1a;https://www.jianshu.com/p/902a89b06271 报错信息&#xff1a; error: no device/emulators found error: device still connecting 解决方案&#xff1a; 重启…

Day_50小结

目录 一. 比较和分析各种查找算法 二. 描述各种排序算法的特点和基本思想&#xff06;比较分析各种排序算法 1. 插入排序 2. 交换排序 3. 选择排序 4. 外部排序 三. 设计一个自己的 Hash 函数和一个冲突解决机制 1. 对于哈希函数的构造&#xff1a; 2. 处理冲突的办法&#…

Mybatis持久层框架 | Lombok搭建

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Lombok Lombok项目是一个java库&#xff0c;它可以自动插入到编辑器和构建工具中&#xff0c;增强java的性能。不需要再写getter、setter或equals方法&#xff0c;只要…

易语言使用node编译的js文件

环境配置 npm install -g cnpm babel-preset-env babel-cli babel-polyfill browserifynpm install -g crypto-js nodejs转js 例如加密模块 browserify -r babel-polyfill -r crypto-js -o es6.txt browserify file.js -o es6.txt易语言 使用v8 推荐 直接生成导入js即…

LMV331TP-TR 滞后比较器实现精确电压比较与判决

文章目录 LMV331TP-TR1. 滞后比较器的基本概念和作用2. LMV331TP-TR 的特性和规格2.1 工作电压范围2.2 输入电压偏置2.3 响应时间 3. LMV331TP-TR 的工作原理3.1 内部结构3.2 滞后器功能的实现原理 4. 实现准确比较和判决的案例 LMV331TP-TR LMV331TP-TR 是一款优秀的滞后比较…

机器学习之逻辑回归模型

1 逻辑回归模型介绍 逻辑回归(Logistic Regression, LR)又称为逻辑回归分析&#xff0c;是一种机器学习算法&#xff0c;属于分类和预测算法中的一种&#xff0c;主要用于解决二分类问题。逻辑回归通过历史数据的表现对未来结果发生的概率进行预测。例如&#xff0c;我们可以将…

web存储(Storage)

目录 1、基本概念 2、功能监测 2.1 测试可用性 2、W3C标准 3、基本方法或属性 4、 Local Storage 4.1 描述 4.2 示例 5、sessionStorage 5.1 描述 5.2 示例 6、StorageEvent&#xff08;存储事件&#xff09; 6.1 构造函数 6.2 实例属性 6.3 实例方法 6.4 响应…

chatgpt赋能python:Python自动缩进详解

Python 自动缩进详解 作为一门面向对象的高级编程语言&#xff0c;Python 其中一个非常重要的特性便是自动缩进。Python 中的代码块是通过缩进来表示的&#xff0c;而不是通过括号或其他方式。这对于刚开始学习 Python 的初学者来说可能是很困难的&#xff0c;但一旦掌握了这一…

开好会议的方法 会议达成共识 明确目标,促成共识 单向会议 互动会议 会议讨论,文档先行 会前文档 会中 3D法则讨论 同步会议,跟进代办 举个栗子 企业管理

目录 开好会的方法 明确目标&#xff0c;促成共识 单向会议 互动会议 会议讨论&#xff0c;文档先行 会前文档 会中 3D法则讨论 同步会议&#xff0c;跟进代办 举个栗子 开好会的方法 会议有时候时间很长&#xff0c;很多无意义内容&#xff0c;如何开出有意义有价值…

Linux学习之权限表现

groupadd grouptest1添加一个叫grouptest1的用户组。 useradd gooduser -g grouptest1添加一个叫gooduser 的用户&#xff0c;并把它添加到grouptest1用户组里边&#xff0c;id gooduser看一下用户的信息。 接下来进行测试用户和用户组权限。 普通文件 在root账户下&#xf…

AI Chat 设计模式:2. 工厂设计模式

本文是该系列的第二篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的旁白。 问题列表 Q.1 介绍下工厂设计模式A.1Q.2 这种设计模式有哪几种形式A.2Q.3 使用c写一个简单工厂的例子A.3Q.4 我…

操作受限的线性表——队列

本文主要内容&#xff1a;介绍了队列的基本概念和基本操作&#xff0c;详细介绍了队列的顺序存储和链式存储。并介绍了循环队列和双端队列&#xff08;以及输入/输出受限的双端队列&#xff09;&#xff0c;及其基本操作。 目录 队列一、队列的基本概念1、基本概念2、基本操作…

计算机视觉-目标检测(二):从R-FCN到YOLO-v3

文章目录 1. R-FCN1.1 动机1.2. R-FCN 网络结构1.3. R-FCN 的损失函数1.4. R-FCN的训练及性能 2. YoLO-v12.1 简介2.2 YOLO-v1网络结构2.3 目标函数2.4 YOLO-v1的优缺点 3. YOLO-v23.1 YOLO-v2相比v1的优化 4. YOLO-v3参考 1. R-FCN 论文链接&#xff1a;R-FCN:Object Detecti…

java格式化数字 NumberFormat及DecimalFormat

一、JavaAPI官方描述 1、NumberFormat NumberFormat帮助您格式化和解析任何区域设置的数字。您的代码可以完全独立于小数点&#xff0c;千位分隔符的区域设置约定&#xff0c;甚至是使用的特定十进制数字&#xff0c;或者数字格式是否为十进制。 2、 DecimalFormat Decimal…

Linux之YUM管理工具

目录 Linux之YUM管理工具 定义 实现YUM的三个机制 RHEL8中yum源变化说明 案例 示例1 --- 建立本地光盘源&#xff08;本地yum源&#xff09; 示例2 --- 配置互联网源 yum(dnf)工具管理软件包 安装软件包 module子命令 案例 yum-config-manager的使用 定义 yum-conf…

Linux系统如何配置网络

Linux系统的三种网络模式&#xff1a; 桥接&#xff1a;可以和外部设备通信&#xff0c;主机和Ubuntu分别使用不同的IP地址NAT&#xff1a;可以和外部设备通信&#xff0c;主机和Ubuntu公用一个IP地址主机&#xff1a;只能和主机通信 在此我们介绍如何配置桥接网络&#xff1…

谓词的介绍与基本使用

&#x1f6a8;谓词 &#x1f6a6;概念 1.返回类型为bool的仿函数 2.接受一个参数—一元谓词 接受一个参数—二元谓词 &#x1f680;1.一元谓词 ⛽使用方法 因为返回值为bool类型&#xff0c;所以经常会将他使用成判断关系的函数 我们使用find_if&#xff08;&#xff09;对…

HBase 2.3.7中snappy压缩配置

本文将介绍如何在HBase 2.3.7中配置snappy压缩。snappy是一种快速的数据压缩和解压缩算法&#xff0c;可以提高HBase的存储空间利用率和读写性能。本文将使用HBase 2.3.7版本&#xff0c;运行在三个Ubuntu系统的虚拟机中&#xff0c;分别作为master和slave节点。 主要步骤如下…

【SpringBoot】SpringBoot Starter 作用及原理

文章目录 前言一、什么是 Starter二、Starter 的作用三、spring 整合组件四、spring-boot 整合组件五、Starter 原理 前言 有没有在入行后直接基于 SpringBoot 开发项目&#xff0c;没有 spring、servlet 开发经历的? 有没有用 SpringBoot 开发项目&#xff0c;但是第一次听…