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

668 阅读7分钟

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

前言

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

源码位置

github1s.com/electron-us…

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 的自动更新功能,同时也为可能的自定义需求提供了思路,如果你有任何问题欢迎和我交流。