手?jǐn)] Electron 自動(dòng)更新,再繁瑣也要搞懂它
大家好,我是楊成功。
Electron 的自動(dòng)更新不會(huì)像 React Native 一樣直接下載 Web 代碼靜默更新,因?yàn)樗€有主進(jìn)程(Node.js)代碼,因此需要走安裝流程。
在 Electron 中,使用第三方包 electron-updater 來(lái)實(shí)現(xiàn)自動(dòng)更新的功能。
為什么不用 autoUpdater?
如果細(xì)看 Electron 文檔,會(huì)發(fā)現(xiàn)官方提供了一個(gè) autoUpdater 功能來(lái)實(shí)現(xiàn)自動(dòng)更新,如圖:
相比 autoUpdater,第三方包 electron-updater 有以下優(yōu)勢(shì):
- 不需要搭建專門的更新服務(wù)(如 Hazel、Nuts 等)。
- 同時(shí)支持 macOS 和 Windows 簽名。
- 支持獲取下載進(jìn)度,等等。
最主要的優(yōu)勢(shì)還是支持自定義更新服務(wù)。將新包上傳到自己的靜態(tài)服務(wù)器,比專門搭建一個(gè)更新服務(wù)更方便。
當(dāng)然獲取下載進(jìn)度也很重要,否則用戶干等著,體驗(yàn)非常差。后面我們會(huì)實(shí)現(xiàn)下載進(jìn)度提示。
接入 electron-updater
安裝 electron-updater,命令如下:
$ yarn add electron-updater
新建 update.js 文件,在該文件中編寫更新邏輯。
定義一個(gè) checkUpdate() 方法,在該方法中執(zhí)行 checkForUpdatesAndNotify() 來(lái)檢測(cè)是否有新版本。
當(dāng)更新完成后,執(zhí)行 quitAndInstall() 方法來(lái)安裝更新后的應(yīng)用包。代碼如下:
const { autoUpdater } = require('electron-updater');
var mainWin = null;
const checkUpdate = (win, ipcMain) => {
autoUpdater.autoDownload = true; // 自動(dòng)下載
autoUpdater.autoInstallOnAppQuit = true; // 應(yīng)用退出后自動(dòng)安裝
mainWin = win;
// 檢測(cè)是否有更新包并通知
autoUpdater.checkForUpdatesAndNotify().catch();
// 監(jiān)聽渲染進(jìn)程的 install 事件,觸發(fā)退出應(yīng)用并安裝
ipcMain.handle('install', () => autoUpdater.quitAndInstall());
};
module.exports = checkUpdate;
上方代碼中,我們通過(guò)監(jiān)聽渲染進(jìn)程的 install 事件來(lái)安裝新包。目的是在下載完新包后先提醒用戶是否安裝,用戶確認(rèn)安裝后才執(zhí)行安裝,強(qiáng)制安裝會(huì)影響用戶使用。
在主進(jìn)程中檢測(cè)更新,并通知用戶。
檢測(cè)更新是在主進(jìn)程中執(zhí)行的,因此在主進(jìn)程中調(diào)用 checkUpdate() 方法。
checkUpdate() 方法接收兩個(gè)參數(shù),分別是渲染進(jìn)程實(shí)例和主進(jìn)程 IPC。主進(jìn)程代碼如下:
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const preload = path.join(__dirname, './preload.js');
const checkUpdate = require('./update.js');
app.whenReady().then(() => {
// 創(chuàng)建瀏覽器窗口(渲染進(jìn)程)
let win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
contextIsolation: false,
preload,
},
});
checkUpdate(win, ipcMain);
});
上方代碼中創(chuàng)建了一個(gè)瀏覽器窗口,并綁定了預(yù)加載腳本(preload.js)。預(yù)加載腳本負(fù)責(zé)主進(jìn)程與渲染進(jìn)程之間的通信。
現(xiàn)在,客戶檢測(cè)更新的邏輯已經(jīng)基本實(shí)現(xiàn)了。但是有新版本的判斷依據(jù)是什么呢?又從哪里下載新版本呢?
下面我們編寫服務(wù)器的邏輯。
客戶端打包,上傳服務(wù)器
檢測(cè)更新的原理,一定是本地應(yīng)用版本和線上版本的對(duì)比,因此需要將應(yīng)用打包并上傳到服務(wù)器。
應(yīng)用打包使用 electron-builder 實(shí)現(xiàn)。
安裝 electron-builder 模塊:
$ yarn add electron-builder
根目錄下創(chuàng)建 electron-builder.json5 配置文件:
{
...
"publish": [
{
"provider": "generic",
"url": "https://xxx/updater/ele-app"
}
]
}
配置文件的可選項(xiàng)有很多,publish 選項(xiàng)是自動(dòng)更新配置。
其中 url 屬性最重要,是線上版本和安裝包的地址,檢測(cè)更新就是通過(guò)這個(gè)地址檢測(cè)。
打包之后,我們就要將新的安裝包和配置上傳到服務(wù)器,并通過(guò)這個(gè)地址訪問(wèn)。
應(yīng)用打包,生成安裝包和版本配置。
應(yīng)用的版本號(hào)取自 package.json 文件中的 version 選項(xiàng)。
假設(shè)當(dāng)前版本號(hào)是 0.1.7,我們首先更新版本號(hào)為 0.1.8,然后使用以下命令打包:
$ electron-builder --win --x64 # 打包 Windows
$ electron-builder --mac --universal # 打包 Mac Intel
$ electron-builder --mac --arm64 # 打包 Mac M1
以 Window 平臺(tái)為例,打包后會(huì)生成多個(gè)文件,以下兩個(gè)需要上傳:
- xxx_0.1.8.exe(安裝包)
- latest.yml(版本配置)
上傳到服務(wù)器后,需要通過(guò) publish 中配置的地址訪問(wèn)到,最終訪問(wèn)地址如下:
- https://xxx/updater/ele-app/xxx_0.1.8.exe:下載安裝包。
- https://xxx/updater/ele-app/latest.yml:檢測(cè)版本更新。
可見(jiàn),latest.yml 文件用于檢測(cè)更新,發(fā)現(xiàn)有更新后才會(huì)下載對(duì)應(yīng)的安裝包。
配置 nginx 使訪問(wèn)地址生效。
我們將上一步的兩個(gè)文件上傳到服務(wù)器的 /data/updater/ele-app 目錄下,然后在 nginx 配置中添加一個(gè) location 如下:
location /updater {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods GET,POST;
alias /data/updater;
sendfile on;
autoindex on;
}
使用 "nginx -s reload" 重新加載配置,訪問(wèn)文件的地址便生效了。
將下載進(jìn)度通知給用戶
在客戶端的 update.js 文件中,我們觸發(fā)了檢測(cè)更新,可以添加事件監(jiān)聽檢測(cè)結(jié)果,如下:
autoUpdater.on('update-available', (info) => {
console.log('有新版本需要更新');
});
autoUpdater.on('update-not-available', (info) => {
console.log('無(wú)需更新');
});
當(dāng)有新版本時(shí),應(yīng)用后臺(tái)會(huì)自動(dòng)下載(因?yàn)槲覀兣渲昧?autoDownload = true),此時(shí)監(jiān)聽下載進(jìn)度并傳給渲染進(jìn)程,用于將其展示到用戶界面上。代碼如下:
autoUpdater.on('download-progress', (prog) => {
mainWin.webContents.send('update', {
speed: Math.ceil(prog.bytesPerSecond / 1000), // 網(wǎng)速
percent: Math.ceil(prog.percent), // 百分比
});
});
autoUpdater.on('update-downloaded', (info) => {
mainWin.webContents.send('downloaded');
// 下載完成后強(qiáng)制用戶安裝,不推薦
// autoUpdater.quitAndInstall();
});
上面代碼中,主進(jìn)程向渲染進(jìn)程發(fā)送了 update 和 downloaded 事件,這些事件需要經(jīng)過(guò)預(yù)加載腳本轉(zhuǎn)發(fā)。渲染進(jìn)程發(fā)送的 install 事件同理。
因此,在預(yù)加載腳本 preload.js 中添加代碼如下:
const { ipcRenderer } = require('electron');
window.elecAPI = {
toInstall: () => ipcRenderer.invoke('install'),
onUpdate: (callback) => ipcRenderer.on('update', callback),
onDownloaded: (callback) => ipcRenderer.on('downloaded', callback),
};
現(xiàn)在,在瀏覽器窗口加載的 HTML 頁(yè)面中,我們可以直接使用上述代碼中的三個(gè)方法。
注意:在預(yù)加載腳本中使用 window.elecAPI 的前提是禁用上下文隔離,即配置 contextIsolation: false,否則會(huì)報(bào)錯(cuò)。
顯示下載進(jìn)度,完成后提示安裝
在 JavaScript 中調(diào)用預(yù)加載腳本中的定義方法,即可獲取到安裝進(jìn)度,并主動(dòng)觸發(fā)安裝。
var update_info = null; // 更新信息
window.elecAPI.onUpdate((_event, info) => {
update_info = info;
});
window.elecAPI.onDownloaded(() => {
update_info = null;
let res = confirm('新版本已下載,是否立即安裝?');
if (res) {
window.elecAPI.toInstall();
}
});
如果你使用 Vue 或 React 框架,直接將 update_info 定義為狀態(tài),即可在頁(yè)面中以任意方式顯示新應(yīng)用的下載進(jìn)度了。
比如下圖,是我的更新提醒頁(yè)面:
下載完成后會(huì)彈框詢問(wèn)是否安裝,確認(rèn)后應(yīng)用會(huì)退出并安裝;如果取消,下一次打開應(yīng)用后還會(huì)繼續(xù)詢問(wèn),直到用戶安裝。
總結(jié)
經(jīng)過(guò)上述的好幾個(gè)步驟,現(xiàn)在要發(fā)布新版本,只需要執(zhí)行 2 個(gè)步驟。
- 更新版本號(hào),打包。
- 上傳到服務(wù)器。
現(xiàn)在用戶重新打開應(yīng)用程序,新包就會(huì)自動(dòng)下載,并看到下載進(jìn)度啦!