美文网首页
Electron实现在线更新与踩坑

Electron实现在线更新与踩坑

作者: 前端波波 | 来源:发表于2020-05-14 17:44 被阅读0次

    前言

    最近帮公司搞桌面应用,从NW.js和Electron中做选择,最后选择了Electron,没啥特别难的点,vue脚手架+vue-cli-plugin-electron-builder一把梭,记录一下在线更新踩的一些坑,顺便给自己做做总结,有未完善的地方见谅。


    简单介绍

    Electron是什么?

    一款开发跨平台桌面应用的框架。
    Windows | Mac | Linux

    为何使用它,有何优缺点?

    优点

    • 快速打包vue/react应用
    • 跨平台

    缺点

    • 体积大,占用内存多

    在线更新的技术前提

    本文讨论的方案,技术前提如下:
    vue/cli脚手架 + vue-cli-plugin-electron-builder插件构建的Electron应用
    (使用Electron框架,直接导入vue项目的,或使用vue-electron的,原理类似,不在本文讨论范围)

    在线更新方案

    使用electron-updater插件进行升级

    安装插件

    npm install electron-updater --save
    

    配置publish

    vue.config.js文件,builder配置内添加publish,如下:

    配置publish项,是为了打包后生成latest.yml文件,该文件是用于判断版本升级的。是打包过程中生成的,为避免自动升级报错,生成后禁止修改文件内容,若文件有误,需要重新打包生成。
    注意,这里有个坑,如果服务器更新地址经常变动,这里的url建议不填写,在主进程获取到url后,通过api重新设置。
    latest.yml文件记录了版本号、更新包的路径、包大小、日期等。

    主进程代码

    electron-updater插件的代码必须在主进程执行

    import { autoUpdater } from 'electron-updater'
    // 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
    function updateHandle (updateConfig) {
      let message = {
        error: 'update error',
        checking: 'updating...',
        updateAva: 'fetch new version and downloading...',
        updateNotAva: 'do not to update'
      }
      // 设置服务器更新地址
      autoUpdater.setFeedURL({
        provider: 'generic',
        url: updateConfig.download
      })
      autoUpdater.on('error', function () {
        sendUpdateMessage(message.error)
      })
      autoUpdater.on('checking-for-update', function () {
        sendUpdateMessage(message.checking)
      })
      // 版本检测结束,准备更新
      autoUpdater.on('update-available', function (info) {
        sendUpdateMessage(message.updateAva)
      })
      autoUpdater.on('update-not-available', function (info) {
        sendUpdateMessage(message.updateNotAva)
      })
      // 更新下载进度事件
      autoUpdater.on('download-progress', function (progressObj) {
        console.log('下载进度百分比>>>', progressObj.percent)
      })
      // 下载完成
      autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
        // 退出且重新安装  
        autoUpdater.quitAndInstall()
      })
      ipcMain.on('checkForUpdate', () => {
        // 执行自动更新检查
        autoUpdater.checkForUpdates()
      })
      // 通过main进程发送事件给renderer进程,提示更新信息
      function sendUpdateMessage (text) {
    mainWindow.webContents.send('message', text)
      }
    }
    export default updateHandle
    
    

    渲染进程代码

    // ####### 请保证updateHandle方法在主进程已经调用过一遍,事件监听都存在
    // 检测更新
    ipcRenderer.send('checkForUpdate')
    

    读取进度条组件

    主进程向渲染进程页面发送进度数据,展示当前更新进度

    <template>
      <div class="update">
          <div class="con">
            <el-progress :percentage="percentage"></el-progress>
            <span>正在检测版本,请稍后</span>
          </div>
      </div>
    </template>
    <script>
    const { ipcRenderer } = require('electron')
    export default {
      name: 'update-loading',
      data () {
        return {
          percentage: 0
        }
      },
      created () {
        let vm = this
        ipcRenderer.on('download-progress', (e, data) => {
          vm.percentage = Number(data)
        })
      }
    }
    

    在线更新实践中的坑

    正确引用autoUpdater

    // 不要引用electron自带的autoUpdater
    // const { autoUpdater } = require('electron')
    import { autoUpdater } from 'electron-updater'
    

    服务器latest-mac.yml文件找不到

    解决方案:
    服务器增加yml文件格式MIME类型映射

    取错本地版本号

    electron-updater自身的bug,会去取Electron的版本;
    解决方案:
    修改electron-updater中appUpdater.js中isUpdateAvailable函数代码

    const pkg=require('../../../package.json');
    // const isLatestVersionNewer = (0, _semver().gt)(latestVersion, currentVersion);
    const isLatestVersionNewer = (0, _semver().gt)(latestVersion, pkg.version);
    

    开发环境提示dev-app-update.yml文件不存在

    解决方案:
    在updateHandle方法内,加入下面代码,地址是本地打包后的app-update.yml文件路径

    if (process.env.NODE_ENV === 'development' && !isMac) {
      autoUpdater.updateConfigPath = path.join(__dirname, 'win-unpacked/resources/app-update.yml')
      // mac的地址是'Contents/Resources/app-update.yml'
    }
    

    下载包缓存导致的更新失败

    解决方案:

    updaterCacheDirName的值与src/main/app-update.yml中的updaterCacheDirName值一致,在windows中会创建一个类似 //C:\Users\Administrator\AppData\Local\electron-updater1\pending文件存储更新下载后的文件"*.exe"和"update-info.json"
    每次更新前,删除本地安装包
    在updateHandle方法内,加入下面代码

    // 更新前,删除本地安装包
    let updaterCacheDirName = 'electron-admin-updater'
    const updatePendingPath = path.join(autoUpdater.app.baseCachePath, updaterCacheDirName, 'pending')
    fs.emptyDir(updatePendingPath)
    

    Mac系统更新需要代码签名

    这里不做深入,有兴趣可以去了解
    https://segmentfault.com/a/1190000012902525

    更新应用的完整代码

    import { ipcMain } from 'electron'
    import { autoUpdater } from 'electron-updater'
    
    // win是所有窗口的引用
    import { createWindow, win } from '../background'
    const path = require('path') // 引入path模块
    const _Store = require('electron-store')
    const fs = require('fs-extra')
    const isMac = process.platform === 'darwin'
    // 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
    function updateHandle (updateConfig = undefined) {
      // electron缓存
      let localStore = new _Store()
      // 更新配置
      updateConfig = updateConfig !== undefined ? updateConfig : localStore.get('updateConfig')
      // 更新前,删除本地安装包 ↓
      let updaterCacheDirName = 'electron-admin-updater'
      const updatePendingPath = path.join(autoUpdater.app.baseCachePath, updaterCacheDirName, 'pending')
      fs.emptyDir(updatePendingPath)
       // 更新前,删除本地安装包 ↑
      let message = {
        error: 'update error',
        checking: 'updating...',
        updateAva: 'fetch new version and downloading...',
        updateNotAva: 'do not to update'
      }
      // 本地开发环境,改变app-update.yml地址
      if (process.env.NODE_ENV === 'development' && !isMac) {
        autoUpdater.updateConfigPath = path.join(__dirname, 'win-unpacked/resources/app-update.yml')
      }
      // 设置服务器更新地址
      autoUpdater.setFeedURL({
        provider: 'generic',
        url: updateConfig.download
      })
      autoUpdater.on('error', function () {
        sendUpdateMessage(message.error)
      })
      autoUpdater.on('checking-for-update', function () {
        sendUpdateMessage(message.checking)
      })
      // 准备更新,打开进度条读取页面,关闭其他页面
      autoUpdater.on('update-available', function (info) {
        sendUpdateMessage(message.updateAva)
        createWindow('update-loading', {
          width: 500,
          height: 300,
          minWidth: 720,
          resizable: false,
          fullscreenable: false,
          frame: false
        })
        for (let key in win) {
          if (key !== 'update-loading') {
            win[key] && win[key].close()
          }
        }
      })
      autoUpdater.on('update-not-available', function (info) {
        sendUpdateMessage(message.updateNotAva)
      })
      // 更新下载进度
      autoUpdater.on('download-progress', function (progressObj) {
        win['update-loading'] && win['update-loading'].webContents.send('download-progress', parseInt(progressObj.percent))
      })
      // 更新完成,重启应用
      autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
        ipcMain.on('isUpdateNow', (e, arg) => {
          // some code here to handle event
          autoUpdater.quitAndInstall()
        })
        win['update-loading'] && win['update-loading'].webContents.send('isUpdateNow')
      })
      ipcMain.on('checkForUpdate', () => {
        // 执行自动更新检查
        autoUpdater.checkForUpdates()
      })
      // 通过main进程发送事件给renderer进程,提示更新信息
      function sendUpdateMessage (text) {
        win['update-loading'] && win['update-loading'].webContents.send('message', message.error)
      }
    }
    export default updateHandle
    
    

    相关文章

      网友评论

          本文标题:Electron实现在线更新与踩坑

          本文链接:https://www.haomeiwen.com/subject/abhxohtx.html