你真的了解 Electron 的自动更新吗?揭秘AppUpdater 类的内部工作原理

简介: 本文由前端徐徐首发,深入探讨了 Electron 的自动更新工作原理,特别是 `electron-builder` 中 `AppUpdater` 类的源码分析,涵盖配置更新源、检查更新、下载更新、安装更新及事件通知等核心功能,帮助开发者更好地理解和使用 Electron 的自动更新机制。

本文首发微信公众号:前端徐徐。

大家好,我是徐徐。今天我们聊聊 Electron 的更新工作原理。

前言

Electron 应用的自动更新是保持应用程序最新、修复 bug 和添加新功能的重要机制。本文将深入分析 electron-builder 的 AppUpdater 类的源码,梳理出完整的更新流程,以帮助开发者更好地理解和使用 Electron 的自动更新功能,相信读了这篇文章你会对 Electron 更新中的一些奇奇怪怪的问题会有一些眉目。

源码位置

https://github1s.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/AppUpdater.ts

AppUpdater类概览

AppUpdaterElectron 自动更新的核心类,它继承自 EventEmitter。 继承 EventEmitter 的主要原因是为了提供事件机制,使 AppUpdater 类能够在其生命周期的不同阶段发出各种事件。这种机制可以让应用的其他部分或外部代码通过监听这些事件,作出相应的反应。这种设计带来了灵活性和可扩展性,使得应用程序可以更好地响应和处理更新过程中的各种情况  这个类的主要职责包括:

  • 配置更新源: setFeedURL 方法设置更新服务器的地址。这一步确定了应用从哪里获取更新。
  • 检查更新: checkForUpdates 方法检查是否有新版本可用。这一步确保应用始终是最新版本,提高用户体验。
  • 下载更新: downloadUpdate 方法负责下载更新文件。下载更新文件是更新过程的关键步骤,保证新版本在用户设备上可用。
  • 安装更新: quitAndInstall 方法退出应用并安装更新。这个方法在确保用户获得更新后,方便地进行更新安装。
  • 事件通知: 继承自 EventEmitter,可以触发更新相关的事件。通过事件机制,开发者可以在更新的各个阶段进行响应,提供用户友好的更新体验。

关键属性:

autoDownload = true; // 是否自动下载更新
autoInstallOnAppQuit = true; // 是否在应用退出时自动安装更新
allowPrerelease = false; // 是否允许更新到预发布版本
allowDowngrade = false; // 是否允许降级

初始化配置

在构造函数中,AppUpdater 会进行一系列初始化操作:

constructor(options: AllPublishOptions | null | undefined, app?: AppAdapter) {
  super();
  
  // 设置当前版本
  const currentVersion = parseVersion(this.app.version);
  this.currentVersion = currentVersion;
  
  // 根据当前版本决定是否允许预发布版本
  this.allowPrerelease = hasPrereleaseComponents(currentVersion);
  
  // 如果提供了选项,设置更新源
  if (options != null) {
    this.setFeedURL(options);
  }
}
  • 设置当前版本: parseVersion(this.app.version) 将当前应用的版本解析并设置为 currentVersion。确保更新机制知道当前版本,以便正确判断是否需要更新。
  • 是否允许预发布版本: hasPrereleaseComponents(currentVersion) 决定是否允许更新到预发布版本。对开发者来说,这一步可以控制是否向用户推送预发布版本。
  • 设置更新源: 如果提供了选项,调用 setFeedURL 设置更新服务器地址。初始化时配置更新源,确保应用知道从哪里获取更新。

检查更新流程

checkForUpdates 方法是更新流程的起点:

async checkForUpdates(): Promise<UpdateCheckResult | null> {
  if (!this.isUpdaterActive()) {
    return Promise.resolve(null);
  }
  this.emit("checking-for-update");
  const result = await this.getUpdateInfoAndProvider();
  const updateInfo = result.info;
  if (!(await this.isUpdateAvailable(updateInfo))) {
    this.emit("update-not-available", updateInfo);
    return { versionInfo: updateInfo, updateInfo };
  }
  this.updateInfoAndProvider = result;
  this.onUpdateAvailable(updateInfo);
  const cancellationToken = new CancellationToken();
  return {
    versionInfo: updateInfo,
    updateInfo,
    cancellationToken,
    downloadPromise: this.autoDownload ? this.downloadUpdate(cancellationToken) : null,
  };
}

这个方法的主要步骤如下:

  • 检查更新器是否激活: 如果更新器未激活,返回空值。确保只有在更新器激活时才进行更新检查。
  • 发出检查更新事件: 触发 checking-for-update 事件,通知开始检查更新。方便开发者提供用户反馈,例如显示“正在检查更新”提示。
  • 获取最新的更新信息: 通过 getUpdateInfoAndProvider 获取更新信息。确保更新机制知道是否有新版本可用。
  • 检查是否有可用更新: 调用 isUpdateAvailable 方法判断是否有新版本。如果没有新版本,触发 update-not-available 事件。
  • 发出可用更新事件: 如果有更新,触发 update-available 事件。通知开发者和用户有新版本可用。
  • 自动下载更新: 如果设置了自动下载,调用 downloadUpdate 方法开始下载更新文件。简化用户操作,自动下载新版本。

下载更新流程  

downloadUpdate 方法负责下载更新文件

downloadUpdate(cancellationToken: CancellationToken = new CancellationToken()): Promise<Array<string>> {
  // ...
  this.downloadPromise = this.doDownloadUpdate({
    updateInfoAndProvider,
    requestHeaders: this.computeRequestHeaders(updateInfoAndProvider.provider),
    cancellationToken,
    disableWebInstaller: this.disableWebInstaller,
    disableDifferentialDownload: this.disableDifferentialDownload,
  });
  // ...
  return this.downloadPromise;
}

这个方法的主要步骤如下:

  • 检查是否已经在下载中: 避免重复下载,确保下载过程只进行一次。
  • 准备下载选项: 包括请求头、取消令牌等,调用 doDownloadUpdate 执行实际的下载操作。确保下载请求正确配置。
  • 处理下载过程中的错误和取消操作: 提供健壮的错误处理和取消机制,确保下载过程可靠。

安装更新

quitAndInstall 方法是一个抽象方法,具体实现在子类中完成:

abstract quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void;

这个方法通常会执行以下操作:

  • 关闭所有应用程序窗口: 确保应用退出前,所有用户操作都已完成。
  • 退出应用程序: 确保应用完全退出,以便安装新版本。
  • 启动安装程序: 安装新版本文件。
  • 静默安装与强制运行: 根据参数决定是否静默安装,是否在安装后强制运行应用。提高用户体验,提供灵活的安装选项。

更新检查策略  

isUpdateAvailable 方法实现了复杂的更新检查逻辑:

private async isUpdateAvailable(updateInfo: UpdateInfo): Promise<boolean> {
  const latestVersion = parseVersion(updateInfo.version);
  
  // 版本比较
  if (isVersionsEqual(latestVersion, this.currentVersion)) {
    return false;
  }
  // 系统要求检查
  const minimumSystemVersion = updateInfo?.minimumSystemVersion;
  if (minimumSystemVersion && isVersionLessThan(release(), minimumSystemVersion)) {
    return false;
  }
  // 分段发布检查
  const isStagingMatch = await this.isStagingMatch(updateInfo);
  if (!isStagingMatch) {
    return false;
  }
  const isLatestVersionNewer = isVersionGreaterThan(latestVersion, this.currentVersion);
  const isLatestVersionOlder = isVersionLessThan(latestVersion, this.currentVersion);
  return isLatestVersionNewer || (this.allowDowngrade && isLatestVersionOlder);
}

这个方法考虑了多个因素:

  • 版本比较: 确保新版本号不同于当前版本。如果新版本号与当前版本号相同,则无需更新。
  • 系统要求检查: 检查当前系统是否满足新版本的最低系统要求。确保新版本在用户设备上可以正常运行。
  • 分段发布检查: 通过 stagingPercentage 控制更新的推送范围。逐步推送更新,降低更新风险。
  • 降级允许: 根据 allowDowngrade 设置决定是否允许安装旧版本。灵活控制更新策略,允许必要时的版本回退。

差量下载

differentialDownloadInstaller 方法实现了差量下载功能:

protected async differentialDownloadInstaller(
  fileInfo: ResolvedUpdateFileInfo,
  downloadUpdateOptions: DownloadUpdateOptions,
  installerPath: string,
  provider: Provider<any>,
  oldInstallerFileName: string
): Promise<boolean> {
  // 下载新旧版本的blockmap文件
  const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u)));
  
  // 使用GenericDifferentialDownloader进行差量下载
  await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]);
  
  return false; // 返回false表示成功进行了差量下载
}

这个方法的主要步骤如下:

  • 下载 blockmap 文件: 下载新旧版本的 blockmap 文件,这些文件描述了安装包的块结构。
  • 比较 blockmap: 使用 GenericDifferentialDownloader 比较 blockmap,只下载变化的部分。减少下载量,加快更新速度。
  • 差量下载成功与否: 如果差量下载成功,返回 false;否则返回 true 表示需要进行完整下载。提高更新效率,减少用户流量消耗。

事件机制  

AppUpdater 提供了丰富的事件:

export type AppUpdaterEvents = {
  error: (error: Error, message?: string) => void
  "checking-for-update": () => void
  "update-available": (info: UpdateInfo) => void
  "update-not-available": (info: UpdateInfo) => void
  "update-downloaded": (event: UpdateDownloadedEvent) => void
  "download-progress": (info: ProgressInfo) => void
  // ...
}

这些事件允许开发者监听更新过程中的各个阶段,例如:

  • error: 当发生错误时触发。方便开发者进行错误处理和日志记录。
  • checking-for-update: 开始检查更新时触发。可以用于显示检查更新的提示。
  • update-available: 发现可用更新时触发。通知用户有新版本可用。
  • update-not-available: 没有可用更新时触发。通知用户当前已经是最新版本。
  • update-downloaded: 更新下载完成时触发。通知用户更新已经下载完毕,可以进行安装。
  • download-progress: 下载进度变化时触发。提供下载进度反馈,提升用户体验。

开发者可以通过监听这些事件来实现自定义的更新行为和用户界面反馈。

结语

通过对 AppUpdater 类的深入分析,我们可以看到 Electron 的自动更新机制是一个复杂而完善的系统。它不仅提供了基本的更新功能,还考虑到了版本控制、差量更新、分段发布等高级特性。开发者可以通过合理配置和利用事件机制,实现灵活而强大的自动更新功能。

理解这些底层实现,有助于我们更好地使用 Electron 的自动更新功能,同时也为可能的自定义需求提供了思路,如果你有任何问题欢迎和我交流。

相关文章
|
1月前
|
安全 前端开发 Windows
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
本文介绍了 Electron 应用在 Windows 中的更新原理,重点分析了 `NsisUpdater` 类的实现。该类利用 NSIS 脚本,通过初始化、检查更新、下载更新、验证签名和安装更新等步骤,确保应用的更新过程安全可靠。核心功能包括差异下载、签名验证和管理员权限处理,确保更新高效且安全。
41 4
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
|
2月前
|
缓存 安全 前端开发
Electron on macOS: 揭秘 MacUpdater 如何实现无缝自动更新?
本文首发于微信公众号“前端徐徐”,详细探讨了 Electron 应用在 macOS 平台上的更新原理。文章分析了 `MacUpdater` 类的实现,包括与 Electron 原生更新器的集成、更新检测和下载、代理服务器管理、环境适配、安全性保障、错误处理和日志记录、更新安装流程控制以及缓存管理等关键功能。通过这些技术细节,展示了如何在 macOS 上实现高效、安全的 Electron 应用更新。
92 0
Electron on macOS: 揭秘 MacUpdater 如何实现无缝自动更新?
|
2月前
|
开发框架 缓存 前端开发
electron-builder 解析:你了解其背后的构建原理吗?
本文首发于微信公众号“前端徐徐”,详细解析了 electron-builder 的工作原理。electron-builder 是一个专为整合前端项目与 Electron 应用的打包工具,负责管理依赖、生成配置文件及多平台构建。文章介绍了前端项目的构建流程、配置信息收集、依赖处理、asar 打包、附加资源准备、Electron 打包、代码签名、资源压缩、卸载程序生成、安装程序生成及最终安装包输出等环节。通过剖析 electron-builder 的原理,帮助开发者更好地理解和掌握跨端桌面应用的构建流程。
210 2
|
应用服务中间件 API nginx
分享下我近期研究, Electron 的自动更新机制
Electron的自动更新机制并不算复杂,但团队内似乎没有相关文档,正好笔者搞明白了,就简单说明一下,以MacOS的arm平台为例说明,具体代码可以参考Postcat的相关配置,本篇文章就不以具体代码举例了。
分享下我近期研究, Electron 的自动更新机制
|
1月前
|
JSON JavaScript 前端开发
开发桌面程序-Electron入门
【10月更文挑战第16天】Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用的框架,嵌入了 Chromium 和 Node.js。本文介绍了如何搭建 Electron 开发环境,包括安装 Node.js、创建项目、配置 main.js 和打包应用。通过简单的步骤,你可以快速创建并运行一个基本的 Electron 应用程序。
开发桌面程序-Electron入门
|
4月前
|
JavaScript 开发工具
Electron 开发过程中主进程的无法看到 console.log 输出怎么办
Electron 开发过程中主进程的无法看到 console.log 输出怎么办
|
5月前
|
前端开发
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
|
7月前
|
移动开发 开发框架 JavaScript
Vue3 Vite electron 开发桌面程序
Vue3 Vite electron 开发桌面程序
351 0
|
前端开发 算法 JavaScript
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(下)
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(下)
198 0
|
存储 Web App开发 JavaScript
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(上)
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(上)
248 0