前言
自动更新检测是指在应用启动或运行过程中,自动检查服务器上的最新版本,并下载和安装更新的功能。这一功能保证了应用始终处于最新状态,减少了用户因手动更新而产生的麻烦。在构建基于 Electron 的桌面应用时,自动更新功能是提升用户体验的重要环节之一。通过实现自动更新,应用可以自动下载和安装最新版本,用户无需手动更新,保持应用的最新状态和安全性。
自动更新的好处:
-
提升用户体验:自动更新减少了用户的操作步骤,确保用户始终使用最新版本的应用,避免了因版本过旧导致的功能不兼容。
-
增强安全性:及时更新到最新版本可以修复已知的安全漏洞,降低因应用漏洞导致的安全风险。
-
简化维护流程:自动更新可以减少开发者手动推送更新的工作量,降低维护成本,确保应用持续稳定运行。
如何实现Electron的自动更新检测?
electron官方提供了一个autoUpdater来进行自动更新,不过要搭建专门的更新服务(如 Hazel、Nuts 等),而第三方的更新服务包electron-updater
可以选择通用的软件更新服务器,更利于学习,所以本文采用的是electron-updater
实现自动更新服务。如何从零创建一个electron项目,请参考此文Electron桌面应用开发实践。假设项目已经有了,要添加自动更新功能。该如何做?
第一步 安装electron-updater
要安装在dependencies
依赖下,否则打包安装之后的应用软件,打开时会提示can't find module electron-updater
。
bash
pnpm add electron-updater
第二步 编写应用更新检测逻辑
在主窗口加载完毕之后,检查是否有新的发布包,判断是否要进行更新。
js
import { app, BrowserWindow, ipcMain, Tray, Menu, screen } from "electron";
import checkUpdate from "./checkUpdate.js";
// 主窗口
let mainWindow;
app.on("ready", async () => {
//每次启动程序,就检查更新
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({
// ...
});
// ...
// 主页面一旦加载完成后就开始执行检查更新
mainWindow.webContents.on("did-finish-load", () => {
checkUpdate();
});
});
checkUpdate
函数的执行流程是:设置更新包的检测url地址,执行更新检测。监听更新包下载出错,是否有高版本的更新包,下载进度,下载完成事件。当软件更新包下载完成时,提示更新。
js
// checkUpdate.js
import { app, dialog } from "electron";
import { autoUpdater } from "electron-updater";
export default function checkUpdate( mainWindow) {
autoUpdater.setFeedURL("http://localhost:9000"); //设置要检测更新url
const isDevelopment = process.env.NODE_ENV === "development";
if (isDevelopment) {
// 强制在开发环境进行更新检查
autoUpdater.forceDevUpdateConfig = true;
}
//检测更新
autoUpdater.checkForUpdates();
//监听'error'事件
autoUpdater.on("error", (err) => {
console.log("出错:", err);
});
//监听'update-available'事件,发现有新版本时触发
autoUpdater.on("update-available", () => {
console.log("found new version");
});
//默认会自动下载新版本,如果不想自动下载,设置autoUpdater.autoDownload = false
autoUpdater.on("download-progress", (progressObj) => {
console.log(`Download speed: ${progressObj.bytesPerSecond}`);
console.log(`Downloaded ${progressObj.percent}%`);
console.log(`Transferred ${progressObj.transferred}/${progressObj.total}`);
});
// 监听'update-downloaded'事件,新版本下载完成时触发
autoUpdater.on("update-downloaded", () => {
dialog
.showMessageBox({
type: "info",
title: "应用更新",
message: "发现新版本,是否更新?",
buttons: ["是", "否"],
})
.then((buttonIndex) => {
if (buttonIndex.response == 0) {
//选择是,则退出程序,安装新版本
autoUpdater.quitAndInstall();
app.quit();
}
});
});
}
如果要在开发环境调试升级功能的话,需要开启强制在开发环境进行更新检查开关,然后手动在package.json中修改version字段,对版本号进行增加。比如当前项目的版本是v0.0.1, 要把package.json中的version版本修改成大于v0.0.1的版本,这里我们修改为v0.0.2
js
{
"version": "0.0.2",
}
第三步 创建静态文件服务器
网上许多文章提到可以把软件更新包发布到github releases
, 这个方案在国内没有全局梯子的情况下行不通,下载更新包时会报网络错误。所以本文采用本地静态服务器来进行演示。
前面我们把软件更新包检测地址配置成了http://localhost:9000
, 现在需要部署一个这样的静态文件服务器。npm serve包可以用来快速地启动一个本地服务器,我们就用它生成一个静态文件服务器,存放Electron应用的更新包。执行下面的命令,生成访问端口号为9000的静态文件服务器。
bash
npm install -g serve
serve -p 9000 ./static-server
另外在electron-builder.json5中添加publish
的配置项,不然Mac系统上无法生成相应的latest-mac.yml
文件。
json
{
publish: [
{
provider: "generic",
url: "http://localhost:9000",
},
],
}
第四步 测试
现在万事俱备,只差一个高版本的软件更新包。我们先在开发环境测试一下更新流程。运行Electron应用打包命令,先打一个v0.0.2的应用包,防止将打包之后的exe和yml文件手动上传到static-server
静态服务器根目录下。
接着把package.json中的version字段修改成0.0.1
, 然后在开发环境运行项目,弹出发现新版本,是否更新的问询框。说明开发环境更新检测没问题。
我们再打一个v0.0.1
的Electron应用包,进行安装。安装完之后打开,看看是否有上面的弹窗,我验证了一下,也是OK的。至此,自动更新的功能已经实现。
踩坑记录
1. 开启DevTools功能后,开发环境会报这样的警告
[8328:0727/175659.432:ERROR:CONSOLE(1)] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
这个警告说的是在调用 Autofill.enable
时失败了,因为在开发工具的协议客户端中未找到 Autofill.enable
方法。通常意味着使用的 Chrome DevTools 协议版本不支持该方法,或者该方法被弃用或未实现。由于不影响使用,不用处理。
2. 警告 updaterCacheDirName is not specified in app-update.yml Was app build using at least electron-builder 20.34.0?
这个错误提示表明 app-update.yml
文件中没有指定 updaterCacheDirName
,还需要确认是否使用了 electron-builder
v20.34.0以上版本。我创建了一个app-update.yml
,填充了这个字段,仍旧有这样的警告,也是不影响使用,就没有去管。
yml
cache:
updaterCacheDirName: ReleaseCache
3. 报错 Skip checkForUpdates because application is not packed and dev update config is not forced
这个错误说的是在开发环境中尝试执行更新检查,但应用未打包,且未强制启用开发更新配置。开启开发更新配置之后报错消失。
js
autoUpdater.forceDevUpdateConfig = true;
4.应用安装之后,界面显示正常,但是无更新提示弹窗弹出。
通过使用electron-log
加日志进行调试,发现是icon图标设置的地址在生产环境找不到,造成后续的逻辑没有被执行导致。
5. 将应用包上传到git releases之后, 未见更新提示弹窗显示
发现使用浏览器插件代理,只有在浏览器上访问github速度是比较流畅的,如果未使用全局代理的话,在Electron应用中,下载git releases中的更新包会报网络错误,导致更新流程出错。
6. webContents的did-finish-load事件不触发
刚开始把 checkUpdate()
放在webContents的did-finish-load事件中执行,结果发现未被调用。后面发现是写法有问题,要移除mainWindow.loadURL
前面的await,想想也是,加上await之后,渲染进程页面已经加载好了,肯定不会再触发did-finish-load事件了。
js
if (VITE_DEV_SERVER_URL) {
// 本地开发环境
await mainWindow.loadURL(VITE_DEV_SERVER_URL + "/main.html");
} else {
// 生产环境
const url = path.join(__dirname, "../");
await mainWindow.loadURL(`file://${url}/dist/main.html`);
}
mainWindow.webContents.on("did-finish-load", () => {
checkUpdate();
});
结尾
如果你只看前面的结果,可能会觉得一切都进展得非常顺利。然而,事实并非如此。在整个过程中,遇到了许多意想不到的小问题。每一个细节都可能带来新的挑战,这些问题虽然看似微小,但却需要花费大量的时间和精力去解决。每一步的前进都是在克服这些障碍后取得的成果。正是这些小问题的不断出现和解决,让人在此过程中不断学习和成长。我们要庆祝结果,也要拥抱过程。