前言

自动更新检测是指在应用启动或运行过程中,自动检查服务器上的最新版本,并下载和安装更新的功能。这一功能保证了应用始终处于最新状态,减少了用户因手动更新而产生的麻烦。在构建基于 Electron 的桌面应用时,自动更新功能是提升用户体验的重要环节之一。通过实现自动更新,应用可以自动下载和安装最新版本,用户无需手动更新,保持应用的最新状态和安全性。

自动更新的好处:

  • 提升用户体验:自动更新减少了用户的操作步骤,确保用户始终使用最新版本的应用,避免了因版本过旧导致的功能不兼容。

  • 增强安全性:及时更新到最新版本可以修复已知的安全漏洞,降低因应用漏洞导致的安全风险。

  • 简化维护流程:自动更新可以减少开发者手动推送更新的工作量,降低维护成本,确保应用持续稳定运行。

如何实现Electron的自动更新检测?

electron官方提供了一个autoUpdater来进行自动更新,不过要搭建专门的更新服务(如 Hazel、Nuts 等),而第三方的更新服务包electron-updater可以选择通用的软件更新服务器,更利于学习,所以本文采用的是electron-updater实现自动更新服务。如何从零创建一个electron项目,请参考此文Electron桌面应用开发实践。假设项目已经有了,要添加自动更新功能。该如何做?

第一步 安装electron-updater

要安装在dependencies依赖下,否则打包安装之后的应用软件,打开时会提示can't find module electron-updater

 1pnpm add electron-updater

第二步 编写应用更新检测逻辑

在主窗口加载完毕之后,检查是否有新的发布包,判断是否要进行更新。

 1import { app, BrowserWindow, ipcMain, Tray, Menu, screen } from "electron";
 2import checkUpdate from "./checkUpdate.js";
 3
 4
 5let mainWindow;
 6
 7app.on("ready", async () => {
 8  
 9
10  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
11
12  mainWindow = new BrowserWindow({
13    
14  });
15
16  
17  
18
19  mainWindow.webContents.on("did-finish-load", () => {
20    checkUpdate();
21  });
22  
23});
24

checkUpdate函数的执行流程是:设置更新包的检测url地址,执行更新检测。监听更新包下载出错,是否有高版本的更新包,下载进度,下载完成事件。当软件更新包下载完成时,提示更新。

 1import { app, dialog } from "electron";
 2import { autoUpdater } from "electron-updater";
 3
 4
 5export default function checkUpdate( mainWindow) {
 6 
 7  autoUpdater.setFeedURL("http://localhost:9000"); 
 8
 9  const isDevelopment = process.env.NODE_ENV === "development";
10  if (isDevelopment) {
11    
12    autoUpdater.forceDevUpdateConfig = true;
13  }
14
15  
16  autoUpdater.checkForUpdates();
17
18  
19  autoUpdater.on("error", (err) => {
20    console.log("出错:", err);
21  });
22
23  
24  autoUpdater.on("update-available", () => {
25    console.log("found new version");
26  });
27
28  
29  autoUpdater.on("download-progress", (progressObj) => {
30    console.log(`Download speed: ${progressObj.bytesPerSecond}`);
31    console.log(`Downloaded ${progressObj.percent}%`);
32    console.log(`Transferred ${progressObj.transferred}/${progressObj.total}`);
33  });
34  
35  
36  autoUpdater.on("update-downloaded", () => {
37    dialog
38      .showMessageBox({
39        type: "info",
40        title: "应用更新",
41        message: "发现新版本,是否更新?",
42        buttons: ["是", "否"],
43      })
44      .then((buttonIndex) => {
45        if (buttonIndex.response == 0) {
46          
47          autoUpdater.quitAndInstall();
48          app.quit();
49        }
50      });
51  });
52}

如果要在开发环境调试升级功能的话,需要开启强制在开发环境进行更新检查开关,然后手动在package.json中修改version字段,对版本号进行增加。比如当前项目的版本是v0.0.1, 要把package.json中的version版本修改成大于v0.0.1的版本,这里我们修改为v0.0.2

 1{
 2  "version": "0.0.2",
 3}

第三步 创建静态文件服务器

网上许多文章提到可以把软件更新包发布到github releases, 这个方案在国内没有全局梯子的情况下行不通,下载更新包时会报网络错误。所以本文采用本地静态服务器来进行演示。

前面我们把软件更新包检测地址配置成了http://localhost:9000, 现在需要部署一个这样的静态文件服务器。npm serve包可以用来快速地启动一个本地服务器,我们就用它生成一个静态文件服务器,存放Electron应用的更新包。执行下面的命令,生成访问端口号为9000的静态文件服务器。

 1npm install -g serve
 2serve -p 9000 ./static-server

另外在electron-builder.json5中添加publish的配置项,不然Mac系统上无法生成相应的latest-mac.yml文件。

 1{
 2  publish: [
 3    {
 4      provider: "generic",
 5      url: "http://localhost:9000",
 6    },
 7  ],
 8}  

第四步 测试

现在万事俱备,只差一个高版本的软件更新包。我们先在开发环境测试一下更新流程。运行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,填充了这个字段,仍旧有这样的警告,也是不影响使用,就没有去管。

 1cache:
 2  updaterCacheDirName: ReleaseCache

3. 报错 Skip checkForUpdates because application is not packed and dev update config is not forced

这个错误说的是在开发环境中尝试执行更新检查,但应用未打包,且未强制启用开发更新配置。开启开发更新配置之后报错消失。

 1 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事件了。

 1if (VITE_DEV_SERVER_URL) {
 2    
 3    await mainWindow.loadURL(VITE_DEV_SERVER_URL + "/main.html");
 4  } else {
 5    
 6    const url = path.join(__dirname, "../");
 7    await mainWindow.loadURL(`file://${url}/dist/main.html`);
 8  }
 9
10
11  mainWindow.webContents.on("did-finish-load", () => {
12    checkUpdate();
13  });
个人笔记记录 2021 ~ 2025