马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
实现步骤
- 配置 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.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 是下载使命对象,可以通过这个对象来获取下载的进度信息。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |