原因

因为我这个electron不是用安装包安装的,所以不能用electron自带的各种更新方式来更新,所以要想另外一种办法来实现更新。

实现方法

首先搜索了一下看看有没有和我一样有类似需求的解决方案,果然在NPM里发现了electron-asar-hot-updater这个插件,因为作为一个简单的electron程序,所有代码都是保存在app.asar文件里的,所以思路就是只需要用新的asar文件替换旧的再重新打开软件就可以实现更新。但是我没有直接用这个插件,原因是我本来就有检查更新的接口,不想沿用插件里的更新服务器接口,。

步骤

检查更新

这一步只需要检查是否需要更新并获得下载链接就可以。
在软件内检查更新,如果需要更新,获取下载链接后通过ipcRenderer发送给electron。

1
electron.ipcRenderer.send('updateEvent', download_url);

下载更新

Electron收到更新事件后,开始下载更新并通过ipcMain传回下载进度并在前端显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ipcMain.on("updateEvent", (event, download_url) => {
downloadFileWorker(download_url, working_path + "/resources"); //working_path = process.cwd();
});

function downloadFileWorker(patchUrl, baseDir) {

const downloadFile = 'update.zip';

let receivedBytes = 0;
let totalBytes = 0;

const req = request({
method: 'GET',
uri: patchUrl
});

const out = fs.createWriteStream(path.join(baseDir, downloadFile));
req.pipe(out);

req.on('response', (data) => {
// get total bytes
totalBytes = parseInt(data.headers['content-length'], 10);
});

req.on('data', (chunk) => {
// get downloaded file size
receivedBytes += chunk.length;
showProgress(receivedBytes, totalBytes);
});

req.on('end', () => {
update(); // download complete
});
}

function showProgress(received, total) {
const percentage = (received * 100) / total;
// send progress to frontend
mainWindow.webContents.send('downloadProgress', percentage.toFixed(0));
}

完成更新

更新的步骤是先关闭electron软件,然后替换文件,替换完成后重启软件,更新完成。

更新的详细信息

改进

这一步在electron-asar-hot-updater是使用了一个C#控制台程序来完成的,但是还是存在依赖的问题(在WIN7系统上需要安装.Net Framework),所以我需要一个更好的解决方法。
我先尝试了用.Net Core来打包一个self contained的控制台程序,这样就不存在依赖问题,但是打包出来的体积太大,放弃。
然后我想是不是可以用Go Lang做一个小程序来完成同样的功能(不知道在WIN7上是否不需要依赖,未测试),没有依赖而且体积小,查了一些Go Lang的基础后,成功写了一个Go版本的Updater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

package main

import (
"fmt"
"os"
"os/exec"
"time"
)

var (
update = ""
asar = ""
executable = ""
)

func main() {
update = os.Args[1]
asar = os.Args[2]
executable = os.Args[3]
time.Sleep(time.Duration(5) * time.Second)
renameFile(update, asar)
openExe(executable)
}

func renameFile(update string, asar string) {
info, fileErr := os.Stat(update)
if os.IsNotExist(fileErr) {
return
}
fmt.Println(info)
err := os.Rename(update, asar)
if err != nil {
fmt.Println(err)
}
}

func openExe(executable string) {
cmd := exec.Command("cmd.exe", "/C", "start", "/b", executable)
if err := cmd.Run(); err != nil {
fmt.Println("Error: ", err)
}
return
}

步骤

下载完成后,把路径信息传给Updater.exe,关闭软件。(我这里把Updater.exe放在了和app.asar同一个文件夹下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function update() {
// working_path = process.cwd();
var downloaded = working_path + '/resources/update.zip'; // 这里下载下来并不是压缩包,只是我替换了后缀来避免一些问题,所以没有解压的步骤,只是改后缀,如果需要自行添加
var updateAsar = working_path + '/resources/update.asar';
var appAsar = working_path + '/resources/app.asar';
let executable = process.execPath;
const WindowsUpdater = working_path + "/resources/updater.exe";

try {
if (!fs.existsSync(downloaded)) return;
// rename downloaded file first
fs.renameSync(downloaded, updateAsar);
winArgs = `${JSON.stringify(WindowsUpdater)} ${JSON.stringify(updateAsar)} ${JSON.stringify(appAsar)} ${JSON.stringify(executable)}`
spawn('cmd', ['/s', '/c', '"' + winArgs + '"'], {
detached: true,
windowsVerbatimArguments: true,
stdio: 'ignore'
})
app.quit();
}
catch (err) {
console.error(err);
}
}

然后Updater接收到更新命令,等待5s(确保软件完全关闭)后替换文件,重新打开软件,更新完成。

缺陷

更新的时候会有一个黑黑的控制台窗口一闪而过,还没找到解决方法,但是除了闪一下并没有任何影响。