实现登录即更新,或实时监听更新
本文介绍的是在App打开启动的时候调用更新,点击下方链接,查看使用WebSocket实现实时通知在线用户更新。
uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传:
实现手持机更新
1.发布到应用商店
2.uiniapp自带版本更新
3.自己开发功能,检测需要更新后从自己的服务器上下载下来更新
这里我们选择自己开发,毕竟不需要证书,和依托于其他平台
app更新更新我们需要解决哪些问题?
1app什么时候知道自己需要更新?
2检测到需要更新后从哪里获取文件?
3如果app一直在线运行,如何实时通知它需要更新?
下面我们逐一解决:
1app什么时候知道自己需要更新?
这里我们使用的是在app每次打开的时候去请求我们后台的接口,拿到最新的app版本(自己定义的),和当前app的版本进行比较。
前提是,你在自己的服务器上上传了apk之后并且记录了在自己的业务表里面。这样你才能比较是否需要更新。(意思就是最好你维护一张表,每次上传插入一条记录)
实现:
因此要卸载App.vue里面
<script>
function requestToJavaBackend() {
uni.request({
url: 'http://*.*.*.*:8080/app/getNewestVersion', // 替换成你的后端 Java 服务的API地址
method: 'GET', // 或 'POST',根据你的需求选择请求方法
success: (res) => {
if(res.data.code === 200){
console.log(res.data.data);
console.log();
console.log(uni.getSystemInfoSync().appVersion+"111111111");
console.log();
console.log(uni.getSystemInfoSync().appVersionCode+"222222");
console.log(platform+"33333333333333333333")
const newVersionName = res.data.data.newVersionName //线上最新版本名
const newVersionCode = res.data.data.newVersionCode; //线上最新版本号
// const selfVersionCode = Number(uni.getSystemInfoSync().appVersion) //当前App版本号
const selfVersionCode = Number(uni.getSystemInfoSync().appVersionCode) //当前App版本号
const platform = uni.getSystemInfoSync().platform //手机平台
//线上版本号高于当前,进行在线升级
if (selfVersionCode < newVersionCode) {
//安卓手机弹窗升级 platform === 'android' || windows
// uni.navigateTo({
// url: '/pages/index/upgrade'
// })
if (platform === 'android') {
uni.navigateTo({
url: '/pages/index/upgrade'
})
} else {
uni.showModal({
title: '发现新版本 ' + newVersionName,
content: '请到App store进行升级',
showCancel: false
})
}
}
}else{
uni.showModal({
title: '版本校验错误',
content: '版本校验错误,联系管理员',
showCancel: false
})
}
},
fail: (err) => {
uni.showModal({
title: '版本校验错误',
content: '版本校验错误,联系管理员',
showCancel: false
})
// 在这里处理请求失败后的逻辑
},
});
}
export default {
onLaunch() {
// 加载系统信息
this.$store.dispatch('SystemInfo');
// 在应用启动时执行一次任务
requestToJavaBackend()
// 每隔一段时间执行一次任务
// setInterval(() => {
// requestToJavaBackend()
// }, 5000); // 30秒
},
onShow() {
},
onHide() {},
methods: {
},
}
</script>
<style lang="scss">
@import "@/uni_modules/uview-ui/index.scss";
@import "@/static/style.scss";
</style>
@GetMapping("/getNewestVersion")
public AjaxResult getNewestVersion(){
HashMap<String, Object> map = new HashMap<>();
map.put("newVersionName", "v");
map.put("newVersionCode", 2);//你的最新版本,建议每次开发完上传之后放在表里,然后去表里拿最新的记录,的版本号
return AjaxResult.success(map);
}
这里检测到2>1之后,就要打开更新页面强制更新(当然你可以不更新,看业务需求)
注意从自己的服务器下载,当前版本小于服务器版本,跳转下载。
自定义下载地址:this.downloadUrl = ‘http://...:8080/app/download’; //下载链接
目录结构
upgrade.vue 更新弹窗
<template>
<view class="upgrade-popup">
<image class="header-bg" src="" 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" >
<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 = 'http://*.*.*.*:8080/app/download'; //下载链接
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); //注册广播
}
@GetMapping("/download")
public void download(String path, HttpServletResponse response) {
try {
//拿到最新的版本
//SysAppVersion 是我自己表的实体,我每次上传后会在这个表里插入一条记录
//下载的时候拿最新的
SysAppVersion newestVersion = sysAppVersionService.getNewestVersion();
File file = new File("d://app//"+newestVersion.getFileName());
String filename = file.getName();
String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
FileInputStream fileInputStream = new FileInputStream(file);
InputStream fis = new BufferedInputStream(fileInputStream);
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
response.reset();
// 设置response的Header
response.setCharacterEncoding("UTF-8");
//Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
//attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
// filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
// 告知浏览器文件的大小
response.addHeader("Content-Length", "" + file.length());
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
outputStream.write(buffer);
outputStream.flush();
} catch (IOException ex) {
ex.printStackTrace();
}
}
结束,非常简单,以上代码cv即用。