likes
comments
collection
share

详解 Electron 应用升级

作者站长头像
站长
· 阅读数 66

Electron 应用升级有两种方案:全量升级和增量升级。

  • 全量升级会下载完整的新版本软件包,覆盖安装到用户电脑上,由于是完整的安装包,里面包含了 Electron 框架,包体积会比较大。
  • 增量升级则是只下载有变动的部分,一般来说是 app.asar 文件,不需要把 Electron 框架都给带进去。

所以一般来讲,当应用升级 Electron 版本了,比如从 9.x.y 升级到 13.x.y,那么就需要用全量升级;而如果 Electron 版本未变,用增量升级的方式会更好,不仅下载速度快,还能节约流量。

全量升级

Electron 内置的 autoUpdater 模块可以完成应用的升级工作,提供了设置更新 URL 的方法:

const server = 'http://your-update-server'
const url = `${server}?platform=${process.platform}&version=${app.getVersion()}`
autoUpdater.setFeedURL({ url })

然后调用下面的方法检查更新:

autoUpdater.checkForUpdates()

这个时候,electron 就会自动请求上面配置的接口,如果有更新,则需要返回一个升级包的下载地址:

{
  "url": "https://new-version-download-url"
  "pub_date": "2022-02-02T02:02:02+08:00",
  "notes": "新版本 5.13.23 发布啦,巴拉巴拉~",
  "version": "5.13.23"
}

其中 url 字段是必须的,它是一个zip包,里面是 dmg 文件。(可不可以是 pkg 文件呢?)其他字段可以由后端自定义。获取升级的过程会触发一系列的事件,我们可以提前设置监听器:

autoUpdater.on('error', callback)
autoUpdater.on('checking-for-update', callback)
autoUpdater.on('update-available', callback)
autoUpdater.on('update-not-available', callback)
autoUpdater.on('update-downloaded', callback)

比较重要的事件是 update-downloaded 事件,我们可以在它的回调里面弹出升级提示框,当用户同意升级之后,调用 autoUpdater 的 quitAndInstall 方法进行重启:

autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
  const dialogOpts = {
    type: 'info',
    buttons: ['Restart', 'Later'],
    title: 'Application Update',
    message: process.platform === 'win32' ? releaseNotes : releaseName,
    detail: 'A new version has been downloaded. Restart the application to apply the updates.',
  }

  dialog.showMessageBox(dialogOpts).then((returnValue) => {
    if (returnValue.response === 0) autoUpdater.quitAndInstall()
  })
})

你可能会问:新的安装包下载到哪个目录下面了呢?一般情况下是 ~/Library/Caches/com.your.company.osx.ShipIt 这里,会生成一些日志文件和临时目录:

ShipItState.plist
ShipIt_stderr.log
ShipIt_stdout.log
update.7bt5Kjs
update.fjskw82

其中 update.xxx 目录就是存放升级文件的。升级的时候,就是先把旧包拷贝到临时目录,然后再把新包复制到 /Application 目录下面即可。

这里有个比较坑的地方是目录或文件的权限问题,具体现象为用户电脑上已经下载并解压成功新的安装包,但是在 quitAndInstall 的时候总是失败,所以会被用户认为是闪退。可能是下面三个目录没有写入权限:

  • /Applications/YourApp.app 应用目录权限
  • ~/Library/Caches/com.your.company.osx 缓存目录权限
  • ~/Library/Caches/com.your.company.osx.ShipIt 升级目录权限

或者下面两个文件没有写入权限:

  • ~/Library/Caches/com.your.company.osx.ShipIt/ShipIt_stdout.log
  • ~/Library/Caches/com.your.company.osx.ShipIt/ShipIt_stderr.log

有时候,我们不想用自带的 autoUpdater 来下载安装包,因为请求不是应用自己发出去的,可能无法做一些鉴权操作等,此时可以自行提前下载更新包到用户电脑上,也然后按照下面的步骤来操作:

  1. 调用接口获取最新版本
  2. 比较当前版本和新版本
  3. 把最新版本下载到本地(临时目录)
  4. 用autoUpdater设置本地路径,然后检查更新
autoUpdater.setFeedURL(temporaryDirPath); 
autoUpdater.checkForUpdates();

有一点需要注意,dmg 格式覆盖安装时,不关系版本号,及时用户下载了比当前电脑上版本更低的旧版软件,也能覆盖。但是 pkg 格式会先比较 info.plist 里面的 CFBundleShortVersionString,如果版本号小于之前的软件,则不会覆盖安装。假如旧版本 info.plist 里面的配置为:

<key>CFBundleShortVersionString</key>
<string>1.0.23</string>
<key>CFBundleVersion</key>
<string>1023</string>

而新版本的配置为:

<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>

则覆盖安装失败,因为 CFBundleShortVersionString 小于或等于 1.0.23,此时改成1.0.24 或 2.0.0 即可。

增量升级

如果只是修改了部分逻辑,影响到 asar 文件,不升级 Electron 版本的话,用户其实没有必要下载一个完整的安装包来覆盖安装,这不仅增加了升级耗时,还会耗费云端的下载流量,能不能只更新 asar 文件呢?其实是可以的,但是在 macOs 和 Windows 系统升级 asar 的逻辑有所不同。

macOS 系统

macOS 上更新 asar 非常简单,只需要下载之后覆盖旧的 asar 包并重启应用即可,而且在应用程序运行的过程中,依然可以操作覆盖,不影响正常使用。

Windows 系统

在 Windows 上,不能直接替换 asar 文件,有两点原因:

  • 如果程序正在运行,是不可以覆盖 asar 文件的,否则会出现锁定提示,必须关闭当前应用
  • 即使应用已关闭,如果用户安装到 C 盘,还需要管理员权限才能覆盖 asar 文件

基于以上两点,社区给了对应的解决方案:

  • 用当前应用启动一个新进程,在新进程里面关闭自己
  • 通过提权服务来启动新进程,在新进程里面进行文件替换

那这个「新进程」是什么呢?这篇文章中给出了解决方案,提供了一个 bat 脚本文件:

@echo off
taskkill /f /im %3
timeout /T 1 /NOBREAK
del /f /q /a %1\app.asar
move %2\update.asar %1
ren %1\update.asar app.asar
explorer.exe %4

看不懂也没关系,可以用 ChatGPT 解释一下这段代码:

详解 Electron 应用升级

其中的关键点是:

  • %1 是 app.asar 所在的目录
  • %2 是 update.asar 所在的目录
  • %3 是软件的进程名称
  • %4 是软件的启动 exe 路径

然后下载 Bat To Exe Converter 软件将这段 bat 转换成 exe,然后安装 @vscode/sudo-prompt 这个提权包,执行以下命令即可:

const sudoPrompt = require('@vscode/sudo-prompt')

/**
 * updateExe: update.exe 所在目录
 * appAsarDir: app.asar 所在目录 
 * updateAsarDir: update.asar 所在目录
 * appName: 软件的进程名称
 * exePath: 可执行文件的路径,即 app.getPath('exe')
 */
sudoPrompt(`"${updateExe}" "${appAsarDir}" ${updateAsarDir} ${appName} "${exePath}"`)

@vscode/sudo-prompt 包会唤起系统弹窗来请求用户授权,例如执行下面的脚本:

var sudo = require('@vscode/sudo-prompt')
sudo.exec('echo hello', options, (error, stdout, stderr) => {
  if (error) throw error
  console.log('stdout: ' + stdout)
})

在 macOS 上会弹出授权提示:

详解 Electron 应用升级

在 Windows 上会弹出提示:

详解 Electron 应用升级

当用户点击同意之后,即可完成增量升级操作。

转载自:https://juejin.cn/post/7250288616533491749
评论
请登录