更新 - 增量更新(二)
不好意思,鸽了挺久了,上一期咱们应用 app.asar.unpacked
实现了 electron 的增量更新,本期咱们介绍的是如何应用 exe 替换 asar
来实现增量更新。
本期内容是基于上一期的内容来解说的,且次要针对于 Windows 零碎(mac 零碎能够对 app.asar 批改)
替换难点
- 在应用了
asar
后,Windows 零碎的 Electron 利用启动后其 app.asar 会被占用,不能对其进行批改和删除,必须完结 Electron 利用的过程后才能够对其进行批改。 - 因为 Windows 的 uac 限度,如果装置在 C 盘的话,批改 app.asar 会有权限问题。
解决思路
其实呢子线程也能够跑 node,然而呢因为主过程完结了咱们并没有 node 环境运行 node 命令,所以此办法不通,当然你也可增加一个编译好的 node 运行子线程 js,然而体积问题得失相当。
在 Windows 下咱们能够用批处理文件 bat 来解决文件,然而 bat 还是有 uac 以及执行时会有 cmd 窗口,咱们能够把写好的 bat 文件转换为 exe 文件来解决这些问题。
- 咱们能够应用 node 衍生独立存在的子过程,把子过程和主过程分来到,完结掉 Electron 利用的过程,用这个子过程进行替换。
- 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 地址:链接