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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348