Electron实现在线更新与踩坑

前言

最近帮公司搞桌面应用,从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



作者:前端波波
链接:https://www.jianshu.com/p/ad3a09aba0c6
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/sd19871122/article/details/108487029
今日推荐