Uniapp 实现app自动检测更新/自动更新功能

打印 上一主题 下一主题

主题 1523|帖子 1523|积分 4569

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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
  1. import appDialog from '@/uni_modules/app-upgrade/js_sdk/dialog';
  2. onLoad(){
  3.     // 检查更新
  4.     this.checkForUpdate()
  5. },
  6. methods: {
  7.     async checkForUpdate() {
  8.         //模拟接口返回数据
  9.         let Response = {
  10.             status: 1,// 0 无新版本 | 1 有新版本
  11.             latestVersionCode: 200,//接口返回的最新版本号,用于对比
  12.             changelog: "1. 优化了界面显示\n2. 修复了已知问题",//更新内容
  13.             path: "xxx.apk"//下载地址
  14.         };
  15.         //获取当前安装包版本号
  16.         const currentVersionCode = await this.getCurrentVersionCode();
  17.         console.log("当前版本号:", currentVersionCode);
  18.         console.log("最新版本号:", Response);
  19.         // 对比版本号
  20.         if (Response.latestVersionCode > currentVersionCode) {
  21.             // 显示更新对话框
  22.             appDialog.show(Response.path, Response.changelog);
  23.         } else {
  24.             uni.showToast({
  25.                 title: '当前已是最新版',
  26.                 icon: 'none'
  27.             });
  28.         }
  29.     },
  30.     getCurrentVersionCode() {
  31.         return new Promise((resolve) => {
  32.             //获取当前安装包版本号
  33.             plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) => {
  34.                 resolve(parseInt(wgtinfo.versionCode));
  35.             });
  36.         });
  37.     }
  38. },
复制代码
 js_sdk/dialog.js
  1. /**
  2. * @Descripttion: app升级弹框
  3. * @Version: 1.0.0
  4. * @Author: leefine
  5. */
  6. import config from '@/upgrade-config.js'
  7. import upgrade from './upgrade'
  8. const {
  9.         title = '发现新版本',
  10.                 confirmText = '立即更新',
  11.                 cancelTtext = '稍后再说',
  12.                 confirmBgColor = '#409eff',
  13.                 showCancel = true,
  14.                 titleAlign = 'left',
  15.                 descriAlign = 'left',
  16.                 icon
  17. } = config.upgrade;
  18. class AppDialog {
  19.         constructor() {
  20.                 this.maskEl = {}
  21.                 this.popupEl = {}
  22.                 this.screenHeight = 600;
  23.                 this.popupHeight = 230;
  24.                 this.popupWidth = 300;
  25.                 this.viewWidth = 260;
  26.                 this.descrTop = 130;
  27.                 this.viewPadding = 20;
  28.                 this.iconSize = 80;
  29.                 this.titleHeight = 30;
  30.                 this.textHeight = 18;
  31.                 this.textSpace = 10;
  32.                 this.popupContent = []
  33.                 this.apkUrl = '';
  34.         }
  35.         // 显示
  36.         show(apkUrl, changelog) {
  37.                 this.drawView(changelog)
  38.                 this.maskEl.show()
  39.                 this.popupEl.show()
  40.                 this.apkUrl = apkUrl;
  41.         }
  42.         // 隐藏
  43.         hide() {
  44.                 this.maskEl.hide()
  45.                 this.popupEl.hide()
  46.         }
  47.         // 绘制
  48.         drawView(changelog) {
  49.                 this.screenHeight = plus.screen.resolutionHeight;
  50.                 this.popupWidth = plus.screen.resolutionWidth * 0.8;
  51.                 this.popupHeight = this.viewPadding * 3 + this.iconSize + 100;
  52.                 this.viewWidth = this.popupWidth - this.viewPadding * 2;
  53.                 this.descrTop = this.viewPadding + this.iconSize + this.titleHeight;
  54.                 this.popupContent = [];
  55.                 if (icon) {
  56.                         this.popupContent.push({
  57.                                 id: 'logo',
  58.                                 tag: 'img',
  59.                                 src: icon,
  60.                                 position: {
  61.                                         top: '0px',
  62.                                         left: (this.popupWidth - this.iconSize) / 2 + 'px',
  63.                                         width: this.iconSize + 'px',
  64.                                         height: this.iconSize + 'px'
  65.                                 }
  66.                         });
  67.                 } else {
  68.                         this.popupContent.push({
  69.                                 id: 'logo',
  70.                                 tag: 'img',
  71.                                 src: '_pic/upgrade.png',
  72.                                 position: {
  73.                                         top: '0px',
  74.                                         left: (this.popupWidth - this.iconSize) / 2 + 'px',
  75.                                         width: this.iconSize + 'px',
  76.                                         height: this.iconSize + 'px'
  77.                                 }
  78.                         });
  79.                 }
  80.                 // 标题
  81.                 if (title) {
  82.                         this.popupContent.push({
  83.                                 id: 'title',
  84.                                 tag: 'font',
  85.                                 text: title,
  86.                                 textStyles: {
  87.                                         size: '18px',
  88.                                         color: '#333',
  89.                                         weight: 'bold',
  90.                                         align: titleAlign
  91.                                 },
  92.                                 position: {
  93.                                         top: this.descrTop - this.titleHeight - this.textSpace + 'px',
  94.                                         left: this.viewPadding + 'px',
  95.                                         width: this.viewWidth + 'px',
  96.                                         height: this.titleHeight + 'px'
  97.                                 }
  98.                         })
  99.                 } else {
  100.                         this.descrTop -= this.titleHeight;
  101.                 }
  102.                 this.drawText(changelog)
  103.                 // 取消
  104.                 if (showCancel) {
  105.                         const width = (this.viewWidth - this.viewPadding) / 2;
  106.                         const confirmLeft = width + this.viewPadding * 2;
  107.                         this.drawBtn('cancel', width, cancelTtext)
  108.                         this.drawBtn('confirm', width, confirmText, confirmLeft)
  109.                 } else {
  110.                         this.drawBtn('confirmBox', this.viewWidth, confirmText)
  111.                 }
  112.                 this.drawBox(showCancel)
  113.         }
  114.         // 描述内容
  115.         drawText(changelog) {
  116.                 if (!changelog) return [];
  117.                 const textArr = changelog.split('')
  118.                 const len = textArr.length;
  119.                 let prevNode = 0;
  120.                 let nodeWidth = 0;
  121.                 let letterWidth = 0;
  122.                 const chineseWidth = 14;
  123.                 const otherWidth = 7;
  124.                 let rowText = [];
  125.                 for (let i = 0; i < len; i++) {
  126.                         // 包含中文
  127.                         if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) {
  128.                                 // 包含字母
  129.                                 let textWidth = ''
  130.                                 if (letterWidth > 0) {
  131.                                         textWidth = nodeWidth + chineseWidth + letterWidth * otherWidth;
  132.                                         letterWidth = 0;
  133.                                 } else {
  134.                                         // 不含字母
  135.                                         textWidth = nodeWidth + chineseWidth;
  136.                                 }
  137.                                 if (textWidth > this.viewWidth) {
  138.                                         rowArrText(i, chineseWidth)
  139.                                 } else {
  140.                                         nodeWidth = textWidth;
  141.                                 }
  142.                         } else {
  143.                                 // 不含中文
  144.                                 // 包含换行符
  145.                                 if (/\n/g.test(textArr[i])) {
  146.                                         rowArrText(i, 0, 1)
  147.                                         letterWidth = 0;
  148.                                 } else if (textArr[i] == '\\' && textArr[i + 1] == 'n') {
  149.                                         rowArrText(i, 0, 2)
  150.                                         letterWidth = 0;
  151.                                 } else if (/[a-zA-Z0-9]/g.test(textArr[i])) {
  152.                                         // 包含字母数字
  153.                                         letterWidth += 1;
  154.                                         const textWidth = nodeWidth + letterWidth * otherWidth;
  155.                                         if (textWidth > this.viewWidth) {
  156.                                                 const preNode = i + 1 - letterWidth;
  157.                                                 rowArrText(preNode, letterWidth * otherWidth)
  158.                                                 letterWidth = 0;
  159.                                         }
  160.                                 } else {
  161.                                         if (nodeWidth + otherWidth > this.viewWidth) {
  162.                                                 rowArrText(i, otherWidth)
  163.                                         } else {
  164.                                                 nodeWidth += otherWidth;
  165.                                         }
  166.                                 }
  167.                         }
  168.                 }
  169.                 if (prevNode < len) {
  170.                         rowArrText(len, -1)
  171.                 }
  172.                 this.drawDesc(rowText)
  173.                 function rowArrText(i, nWidth = 0, type = 0) {
  174.                         const typeVal = type > 0 ? 'break' : 'text';
  175.                         rowText.push({
  176.                                 type: typeVal,
  177.                                 content: changelog.substring(prevNode, i)
  178.                         })
  179.                         if (nWidth >= 0) {
  180.                                 prevNode = i + type;
  181.                                 nodeWidth = nWidth;
  182.                         }
  183.                 }
  184.         }
  185.         // 描述
  186.         drawDesc(rowText) {
  187.                 rowText.forEach((item, index) => {
  188.                         if (index > 0) {
  189.                                 this.descrTop += this.textHeight;
  190.                                 this.popupHeight += this.textHeight;
  191.                         }
  192.                         this.popupContent.push({
  193.                                 id: 'content' + index + 1,
  194.                                 tag: 'font',
  195.                                 text: item.content,
  196.                                 textStyles: {
  197.                                         size: '14px',
  198.                                         color: '#666',
  199.                                         align: descriAlign
  200.                                 },
  201.                                 position: {
  202.                                         top: this.descrTop + 'px',
  203.                                         left: this.viewPadding + 'px',
  204.                                         width: this.viewWidth + 'px',
  205.                                         height: this.textHeight + 'px'
  206.                                 }
  207.                         })
  208.                         if (item.type == 'break') {
  209.                                 this.descrTop += this.textSpace;
  210.                                 this.popupHeight += this.textSpace;
  211.                         }
  212.                 })
  213.         }
  214.         // 按钮
  215.         drawBtn(id, width, text, left = this.viewPadding) {
  216.                 let boxColor = confirmBgColor,
  217.                         textColor = '#ffffff';
  218.                 if (id == 'cancel') {
  219.                         boxColor = '#f0f0f0';
  220.                         textColor = '#666666';
  221.                 }
  222.                 this.popupContent.push({
  223.                         id: id + 'Box',
  224.                         tag: 'rect',
  225.                         rectStyles: {
  226.                                 radius: '6px',
  227.                                 color: boxColor
  228.                         },
  229.                         position: {
  230.                                 bottom: this.viewPadding + 'px',
  231.                                 left: left + 'px',
  232.                                 width: width + 'px',
  233.                                 height: '40px'
  234.                         }
  235.                 })
  236.                 this.popupContent.push({
  237.                         id: id + 'Text',
  238.                         tag: 'font',
  239.                         text: text,
  240.                         textStyles: {
  241.                                 size: '14px',
  242.                                 color: textColor
  243.                         },
  244.                         position: {
  245.                                 bottom: this.viewPadding + 'px',
  246.                                 left: left + 'px',
  247.                                 width: width + 'px',
  248.                                 height: '40px'
  249.                         }
  250.                 })
  251.         }
  252.         // 内容框
  253.         drawBox(showCancel) {
  254.                 this.maskEl = new plus.nativeObj.View('maskEl', {
  255.                         top: '0px',
  256.                         left: '0px',
  257.                         width: '100%',
  258.                         height: '100%',
  259.                         backgroundColor: 'rgba(0,0,0,0.5)'
  260.                 });
  261.                 this.popupEl = new plus.nativeObj.View('popupEl', {
  262.                         tag: 'rect',
  263.                         top: (this.screenHeight - this.popupHeight) / 2 + 'px',
  264.                         left: '10%',
  265.                         height: this.popupHeight + 'px',
  266.                         width: '80%'
  267.                 });
  268.                 // 白色背景
  269.                 this.popupEl.drawRect({
  270.                         color: '#ffffff',
  271.                         radius: '8px'
  272.                 }, {
  273.                         top: this.iconSize / 2 + 'px',
  274.                         height: this.popupHeight - this.iconSize / 2 + 'px'
  275.                 });
  276.                 this.popupEl.draw(this.popupContent);
  277.                 this.popupEl.addEventListener('click', e => {
  278.                         const maxTop = this.popupHeight - this.viewPadding;
  279.                         const maxLeft = this.popupWidth - this.viewPadding;
  280.                         const buttonWidth = (this.viewWidth - this.viewPadding) / 2;
  281.                         if (e.clientY > maxTop - 40 && e.clientY < maxTop) {
  282.                                 if (showCancel) {
  283.                                         // 取消
  284.                                         // if(e.clientX>this.viewPadding && e.clientX<maxLeft-buttonWidth-this.viewPadding){}
  285.                                         // 确定
  286.                                         if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) {
  287.                                                 upgrade.checkOs(this.apkUrl)
  288.                                         }
  289.                                 } else {
  290.                                         if (e.clientX > this.viewPadding && e.clientX < maxLeft) {
  291.                                                 upgrade.checkOs(this.apkUrl)
  292.                                         }
  293.                                 }
  294.                                 this.hide()
  295.                         }
  296.                 });
  297.         }
  298. }
  299. export default new AppDialog()
复制代码
js_sdk/upgrade.js
  1. /**
  2. * @Descripttion: app下载更新
  3. * @Version: 1.0.0
  4. * @Author: leefine
  5. */
  6. import config from '@/upgrade-config.js'
  7. const { upType=0 }=config.upgrade;
  8. class Upgrade{
  9.        
  10.         // 检测平台
  11.         checkOs(apkUrl){
  12.                 uni.getSystemInfo({
  13.                         success:(res) => {
  14.                                 if(res.osName=="android"){
  15.                                         if(upType==1 && packageName){
  16.                                                 plus.runtime.openURL('market://details?id='+packageName)
  17.                                         }else{
  18.                                                 this.downloadInstallApp(apkUrl)
  19.                                         }
  20.                                 }else if(res.osName=='ios' && appleId){
  21.                                         // apple id 在 app conection 上传的位置可以看到 https://appstoreconnect.apple.com
  22.                                         plus.runtime.launchApplication({
  23.                                                 action: `itms-apps://itunes.apple.com/cn/app/id${appleId}?mt=8`
  24.                                         }, function(err) {
  25.                                                 uni.showToast({
  26.                                                         title:err.message,
  27.                                                         icon:'none'
  28.                                                 })
  29.                                         })
  30.                                 }
  31.                         }  
  32.                 })
  33.         }
  34.        
  35.         // 下载更新
  36.         downloadInstallApp(apkUrl){
  37.                 const dtask = plus.downloader.createDownload(apkUrl, {}, function (d,status){
  38.                         // 下载完成  
  39.                         if (status == 200){
  40.                                 plus.runtime.install(plus.io.convertLocalFileSystemURL(d.filename),{},{},function(error){  
  41.                                         uni.showToast({  
  42.                                                 title: '安装失败',
  43.                                                 icon:'none'
  44.                                         });  
  45.                                 })
  46.                         }else{
  47.                                 uni.showToast({
  48.                                         title: '更新失败',
  49.                                         icon:'none'
  50.                                 });
  51.                         }
  52.                 });
  53.                 this.downloadProgress(dtask);
  54.         }
  55.        
  56.         // 下载进度
  57.         downloadProgress(dtask){
  58.                 try{
  59.                         dtask.start(); //开启下载任务
  60.                         let prg=0;
  61.                         let showLoading=plus.nativeUI.showWaiting('正在下载');
  62.                         dtask.addEventListener('statechanged',function(task,status){
  63.                                 // 给下载任务设置监听
  64.                                 switch(task.state){
  65.                                         case 1:
  66.                                                 showLoading.setTitle('正在下载');
  67.                                                 break;
  68.                                         case 2:
  69.                                                 showLoading.setTitle('已连接到服务器');
  70.                                                 break;
  71.                                         case 3:
  72.                                                 prg=parseInt((parseFloat(task.downloadedSize)/parseFloat(task.totalSize))*100);
  73.                                                 showLoading.setTitle('正在下载'+prg+'%');
  74.                                                 break;
  75.                                         case 4:
  76.                                                 // 下载完成
  77.                                                 plus.nativeUI.closeWaiting();
  78.                                                 break;
  79.                                 }
  80.                         })
  81.                 }catch(e){
  82.                         plus.nativeUI.closeWaiting();
  83.                         uni.showToast({
  84.                                 title: '更新失败',
  85.                                 icon:'none'
  86.                         })
  87.                 }
  88.         }
  89.        
  90. }
  91. export default new Upgrade()
复制代码
upgrade-config.js
  1. export default {
  2.     upgrade:{
  3.         packageName:'',
  4.         appleId:'',
  5.         upType:0,
  6.         timer:24,
  7.         icon:'/static/logo.png',
  8.         title:'发现新版本',
  9.         confirmText:'立即更新',
  10.         cancelTtext:'稍后再说',
  11.         confirmBgColor:'#409eff',
  12.         showCancel:true,
  13.         titleAlign:'left',
  14.         descriAlign:'left'
  15.     }
  16. }
复制代码

结果图:



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企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

灌篮少年

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表