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();
  });

结尾

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

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端