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