Electron自动更新实践

前言

自动更新检测是指在应用启动或运行过程中,自动检查服务器上的最新版本,并下载和安装更新的功能。这一功能保证了应用始终处于最新状态,减少了用户因手动更新而产生的麻烦。在构建基于 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();
  });

结尾

如果你只看前面的结果,可能会觉得一切都进展得非常顺利。然而,事实并非如此。在整个过程中,遇到了许多意想不到的小问题。每一个细节都可能带来新的挑战,这些问题虽然看似微小,但却需要花费大量的时间和精力去解决。每一步的前进都是在克服这些障碍后取得的成果。正是这些小问题的不断出现和解决,让人在此过程中不断学习和成长。我们要庆祝结果,也要拥抱过程。

相关推荐
QGC二次开发几秒前
Vue3 : Pinia的性质与作用
前端·javascript·vue.js·typescript·前端框架·vue
云草桑12 分钟前
逆向工程 反编译 C# net core
前端·c#·反编译·逆向工程
布丁椰奶冻17 分钟前
解决使用nvm管理node版本时提示npm下载失败的问题
前端·npm·node.js
Leyla43 分钟前
【代码重构】好的重构与坏的重构
前端
影子落人间1 小时前
已解决npm ERR! request to https://registry.npm.taobao.org/@vant%2farea-data failed
前端·npm·node.js
世俗ˊ1 小时前
CSS入门笔记
前端·css·笔记
子非鱼9211 小时前
【前端】ES6:Set与Map
前端·javascript·es6
6230_1 小时前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛2 小时前
HTML 揭秘:HTML 编码快速入门
前端·html