乐趣区

关于electron:从零开始的electron开发更新增量更新二

更新 - 增量更新(二)

不好意思,鸽了挺久了,上一期咱们应用 app.asar.unpacked 实现了 electron 的增量更新,本期咱们介绍的是如何应用 exe 替换 asar 来实现增量更新。
本期内容是基于上一期的内容来解说的,且次要针对于 Windows 零碎(mac 零碎能够对 app.asar 批改)

替换难点

  1. 在应用了 asar 后,Windows 零碎的 Electron 利用启动后其 app.asar 会被占用,不能对其进行批改和删除,必须完结 Electron 利用的过程后才能够对其进行批改。
  2. 因为 Windows 的 uac 限度,如果装置在 C 盘的话,批改 app.asar 会有权限问题。

解决思路

其实呢子线程也能够跑 node,然而呢因为主过程完结了咱们并没有 node 环境运行 node 命令,所以此办法不通,当然你也可增加一个编译好的 node 运行子线程 js,然而体积问题得失相当。
在 Windows 下咱们能够用批处理文件 bat 来解决文件,然而 bat 还是有 uac 以及执行时会有 cmd 窗口,咱们能够把写好的 bat 文件转换为 exe 文件来解决这些问题。

  1. 咱们能够应用 node 衍生独立存在的子过程,把子过程和主过程分来到,完结掉 Electron 利用的过程,用这个子过程进行替换。
  2. uac 限度能够应用 exe 文件来获取管理员权限进行解决。

解决方案

1. 打包批改

咱们这里去除之前的 app.asar.unpacked 打包,把之前 vue.config 的正文掉,这样咱们打包就只有 asar 文件了

// extraResources: [{
//   from: "dist_electron/bundled",
//   to: "app.asar.unpacked",
//   filter: [
//     "!**/icons",
//     "!**/preload.js",
//     "!**/node_modules",
//     "!**/background.js"
//   ]
// }],
// files: [
//   "**/icons/*",
//   "**/preload.js",
//   "**/node_modules/**/*",
//   "**/background.js"
// ],

2. 构建增量 zip

上一期构建增量 zip 时应用了 afterPack 钩子,这里咱们对其批改,把新版本的 app.asar 重命名为update.asar,放入增量的 app.zip 包里,不理解的能够看看上一期的内容

const path = require('path')
const AdmZip = require('adm-zip')
const fse = require('fs-extra')

exports.default = async function(context) {
  let targetPath
  if(context.packager.platform.nodeName === 'darwin') {targetPath = path.join(context.appOutDir, `${context.packager.appInfo.productName}.app/Contents/Resources`)
  } else {targetPath = path.join(context.appOutDir, './resources')
  }
  const asar = path.join(targetPath, './app.asar')
  fse.copySync(asar, path.join(context.outDir, './update.asar'))
  var zip = new AdmZip()
  zip.addLocalFile(path.join(context.outDir, './update.asar'))
  zip.writeZip(path.join(context.outDir, 'app.zip'))
  fse.removeSync(path.join(context.outDir, './update.asar'))
}

3. 模仿接口

同上一期,咱们这里批改了 upDateUrl 和 upDateExe,upDateUrl 是增量 zip,upDateExe 是咱们用来替换 asar 的 exe 文件。

{
  "code": 200,
  "success": true,
  "data": {
    "forceUpdate": false,
    "fullUpdate": false,
    "upDateUrl": "http://127.0.0.1:4000/app.zip",
    "upDateExe": "http://127.0.0.1:4000/update.exe",
    "restart": false,
    "message": "我要升级成 0.0.2",
    "version": "0.0.2"
  }
}

4. 加载策略批改

这里咱们再把加载策略批改为加载 app.asar 里的文件

// createProtocol('app', path.join(resources, './app.asar.unpacked'))
createProtocol('app')

5. 渲染过程增量更新

这个没变动,同上一期

6. 主过程解决

咱们把之前的主过程下载批改一下,先判断 update.exe 是否存在,不存在的话先下载 update.exe 放入 app.getPath('userData') 里:

win:C:\Users\Administrator(你的用户)\AppData\Roaming\<app name>\
mac:/Users/(你的用户)/Library/Application Support/<app name>

app.getPath('userData')这个门路呢,比拟非凡,装置之后就存在了,是数据文件,你的 indexDB,localStorage 等都存在这外面,软件的全量更新,卸载都不会改变这个文件里的货色,
咱们把 update.exe 放入这里,防止全量更新后重新安装。之后下载增量更新包解压到 resourcesh 中(update.asar),也就是和 app.asar 同级目录,删除 zip 包,运行 app.exit(0) 敞开主过程

import downloadFile from './downloadFile'
import {app} from 'electron'
const fse = require('fs-extra')
const path = require('path')
const AdmZip = require('adm-zip')

export default async (data) => {
  const resourcesPath = process.resourcesPath
    if (!fse.pathExistsSync(path.join(app.getPath('userData'), './update.exe'))) {await downloadFile({ url: data.upDateExe, targetPath: app.getPath('userData') })
    }
    downloadFile({url: data.upDateUrl, targetPath: resourcesPath}).then(async (filePath) => {const zip = new AdmZip(filePath)
      zip.extractAllToAsync(resourcesPath, true, (err) => {if (err) {console.error(err)
          return
        }
        fse.removeSync(filePath)
        app.exit(0)
      })
    }).catch(err => {console.log(err)
    })
}

7. 子过程解决

主过程敞开后会触发 quit 事件,咱们在这个事件里检测 update.exe 以及 update.asar 是否同时存在,
同时存在的话咱们用 spawn 开启一个子过程运行咱们的 update.exe,并且传入resourcesPath(app.asar 所在目录门路),app.getPath('exe')(咱们软件的启动门路),
应用 child.unref() 让子过程和父过程拆散,能够不退出子过程的状况下退出父过程。

const {spawn} = require('child_process')
const fse = require('fs-extra')
const fs = require('fs')
const resourcesPath = process.resourcesPath

app.on('quit', () => {console.log('quit')
  if (fse.pathExistsSync(path.join(app.getPath('userData'), './')) && fse.pathExistsSync(path.join(resourcesPath, './update.asar'))) {const logPath = app.getPath('logs')
    const out = fs.openSync(path.join(logPath, './out.log'), 'a')
    const err = fs.openSync(path.join(logPath, './err.log'), 'a')
    const child = spawn(`"${path.join(app.getPath('userData'),'./update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
      detached: true,
      shell: true,
      stdio: ['ignore', out, err]
    })
    child.unref()}
})

也就是说这里是父过程退出后,子过程执行咱们的 exe,替换 app.asar,out 和 err 是将子过程执行的日志重定向到 app.getPath('logs') 中,这个门路和 electron-log 不一样(你也能够本人设置为 electron-log 门路一样)

win:C:\Users\Administrator(你的用户)\AppData\Roaming\<app name>\<app productName>\logs
mac: ~/Library/Logs/<app name>?应该是这个上面的,这个我没验证

8. 构建 exe

筹备工作实现了,这里咱们编写 exe,其实这个没啥难度的,咱们应用 bat 脚本打包成 exe 就行。
update.bat

@echo off
timeout /T 1 /NOBREAK
del /f /q /a %1\app.asar
ren %1\update.asar app.asar
start "" %2

简略解释一下吧,%1 和 %2 为运行脚本传入的参数,比方 update.bat aaa bbb,那么 %1 为 aaa,%2 为 bbb,下面咱们用 spawn 运行 exe 时传入的,
也就是 %1 为 resourcesPath,%2 为软件的启动 exe,咱们运行 bat 脚本,先暂停 1 秒钟保障主过程退出了,而后删除 app.asar,将 update.asar 重命名为 app.asar,启动软件 exe。
一个简略的 bat 替换就实现了,咱们下载 Bat To Exe Converter 这个软件,将 update.bat 转换为 update.exe,而后将 update.exe 放入咱们的 http-server 目录中。运行软件检测更新,看看更新是否实现。

补充阐明

spawn(`"${path.join(app.getPath('userData'),'./update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
  detached: true,
  shell: true,
  stdio: ['ignore', out, err]
})

这里有同学可能会有疑难,为什么要在几个门路外加一个 ””,这是因为 node 运行脚本的路径名中蕴含空格的话,须要加上引号,bat 解决也一样,比方咱们的软件装置在 c 盘,
C:\Program Files\electronVueDEV,最常见的问题就是 Program Files 这里有个空格,这会导致 bat 命令里有这样的门路的话会解决失败,所以咱们的门路都加了引号的。

本系列更新只有利用周末和下班时间整顿,比拟多的内容的话更新会比较慢,心愿能对你有所帮忙,请多多 star 或点赞珍藏反对一下

本文地址:链接
本文 github 地址:链接

退出移动版