Electron控制应用是否更新

2,884 阅读4分钟

更新需要提示用户,需要控制应用是否更新

1. 方案一

在检测到更新后提示用户,让用户选择更新。

设置autoDownload参数为false,让应用检测到更新不自动下载,改成手动下载更新包。

通过在钩子update-available中,加入对话框提示用户,让用户选择。

response为0用户选择确定,触发downloadUpdate方法下载应用更新包进行后续更新操作。否则,不下载更新包。

如果我们不配置autoDownload为false,那么问题来了:在弹出对话框的同时,用户还来不及选择,应用自动下载并且更新完成,做不到阻塞。

本文首发于公众号「前端keep」,欢迎关注。

重要代码如下:

autoUpdater.autoDownload = false

update-available钩子中弹出对话框

autoUpdater.on('update-available', (ev, info) => {
  // // 不可逆过程
  const options = {
    type: 'info',
    buttons: ['确定', '取消'],
    title: '更新提示',
    // ${info.version} Cannot read property 'version' of undefined
    message: '发现有新版本,是否更新?',
    cancelId: 1
  }
  dialog.showMessageBox(options).then(res => {
    if (res.response === 0) {
      autoUpdater.downloadUpdate()
      logger.info('下载更新包成功')
      sendStatusToWindow('下载更新包成功');
    } else {
      return;
    }
  })
})

2. 方案二

在更新下载完后提示用户,让用户选择更新。

先配置参数autoInstallOnAppQuit为false,阻止应用在检测到更新包后自动更新。

在钩子update-downloaded中加入对话框提示用户,让用户选择。

response为0用户选择确定,更新应用。否则,当前应用不更新。

如果我们不配置autoInstallOnAppQuit为false,那么问题是:虽然第一次应用不更新,但是第二次打开应用,应用马上关闭,还没让我们看到主界面,应用暗自更新,重点是更新完后不重启应用。

重要代码如下:

// 表示下载包不自动更新
autoUpdater.autoInstallOnAppQuit = false
在update-downloaded钩子中弹出对话框
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName) => {
  logger.info('下载完成,更新开始')
  sendStatusToWindow('下载完成,更新开始');
  // Wait 5 seconds, then quit and install
  // In your application, you don't need to wait 5 seconds.
  // You could call autoUpdater.quitAndInstall(); immediately
  const options = {
    type: 'info',
    buttons: ['确定', '取消'],
    title: '应用更新',
    message: process.platform === 'win32' ? releaseNotes : releaseName,
    detail: '发现有新版本,是否更新?'
  }
  dialog.showMessageBox(options).then(returnVal => {
    if (returnVal.response === 0) {
      logger.info('开始更新')
      setTimeout(() => {
        autoUpdater.quitAndInstall()
      }, 5000);
    } else {
      logger.info('取消更新')
      return
    }
  })
});

3. 源码分析

未打包目录位于: electron-builder/packages/electron-updater/src/AppUpdater.ts中。 打包后在electron-updater\out\AppUpdater.d.ts中

  1. 首先进入checkForUpdates()方法,开始检测更新
  2. 正在更新不需要进入
  3. 开始更新前判断autoDownload,为true自动下载,为false不下载等待应用通知。
export declare abstract class AppUpdater extends EventEmitter {
    /**
     * 当被发现有更新时,是否要自动下载更新
     * 场景:可以适用于electron检查更新包提示,用户操作是否需要更新
     */
    autoDownload: boolean;
    /**
     * 在app.quit()后,是否自动将下载下载的更新包更新
     * 场景:可以适用于electron下载完更新包提示,用户操作是否需要更新。在第二次打开应用,应用不会自动更新。
     */
    autoInstallOnAppQuit: boolean;
}


/**
 * 检测是否需要更新
 */
checkForUpdates(): Promise < UpdateCheckResult > {
  let checkForUpdatesPromise = this.checkForUpdatesPromise
  // 正在检测更新跳过
  if (checkForUpdatesPromise != null) {
    this._logger.info("Checking for update (already in progress)")
    return checkForUpdatesPromise
  }

  const nullizePromise = () => this.checkForUpdatesPromise = null
  // 开始检测更新
  this._logger.info("Checking for update")
  checkForUpdatesPromise = this.doCheckForUpdates()
  .then(it => {
    nullizePromise()
    return it
  })
  .catch(e => {
    nullizePromise()
    this.emit("error", e, `Cannot check for updates: ${(e.stack || e).toString()}`)
    throw e
  })

  this.checkForUpdatesPromise = checkForUpdatesPromise
  return checkForUpdatesPromise
}
// 检测更新具体函数
private async doCheckForUpdates(): Promise < UpdateCheckResult > {
  // 触发  checking-for-update 钩子
  this.emit("checking-for-update")
  // 取更新信息
  const result = await this.getUpdateInfoAndProvider()
  const updateInfo = result.info
  //  判断更新信息是否有效
  if (!await this.isUpdateAvailable(updateInfo)) {
    this._logger.info(`Update for version ${this.currentVersion} is not available (latest version: ${updateInfo.version}, downgrade is ${this.allowDowngrade ? "allowed" : "disallowed"}).`)
    this.emit("update-not-available", updateInfo)
    return {
      versionInfo: updateInfo,
      updateInfo,
    }
  }

  this.updateInfoAndProvider = result
  this.onUpdateAvailable(updateInfo)

  const cancellationToken = new CancellationToken()
  //noinspection ES6MissingAwait
  // 如果设置autoDownload为true,则开始自动下载更新包,否则不下载
  return {
    versionInfo: updateInfo,
    updateInfo,
    cancellationToken,
    downloadPromise: this.autoDownload ? this.downloadUpdate(cancellationToken) : null
  }
}

如果需要配置updater中的其他参数达到某种功能,我们可以仔细查看其中的配置项。

export abstract class AppUpdater extends EventEmitter {
    /**
     * 当被发现有更新时,是否要自动下载更新
     * 场景:可以适用于electron检查更新包提示,用户操作是否需要更新
     */
    autoDownload: boolean;
    /**
     * 在app.quit()后,是否自动将下载下载的更新包更新
     * 场景:可以适用于electron下载完更新包提示,用户操作是否需要更新。在第二次打开应用,应用不会自动更新。
     */
    autoInstallOnAppQuit: boolean;
    /**
   *   GitHub提供者。
    是否允许升级到预发布版本。
    如果应用程序版本包含预发布组件,默认为“true”。0.12.1-alpha.1,这里alpha是预发布组件),否则“false”。
    allowDowngrade设置为true,则应用允许降级。
   */
    allowPrerelease: boolean;
    /**
     * GitHub提供者。
    获取所有发布说明(从当前版本到最新版本),而不仅仅是最新版本。
    @default false
     */
    fullChangelog: boolean;
    /**
     *是否允许版本降级(当用户从测试通道想要回到稳定通道时)。
     *仅当渠道不同时考虑(根据语义版本控制的预发布版本组件)。
     * @default false
     */
    allowDowngrade: boolean;
    /**
     * 当前应用的版本
     */
    readonly currentVersion: SemVer;
    private _channel;
    protected downloadedUpdateHelper: DownloadedUpdateHelper | null;
    /**
     * 获取更新通道。
      不适用于GitHub。
      从更新配置不返回“channel”,仅在之前设置的情况下。
     */
    get channel(): string | null;
    /**
     * 设置更新通道。
   不适用于GitHub。
   覆盖更新配置中的“channel”。
   “allowDowngrade”将自动设置为“true”。
   如果这个行为不适合你,明确后简单设置“allowDowngrade”。
     */
    set channel(value: string | null);
    /**
     * 请求头
     */
    requestHeaders: OutgoingHttpHeaders | null;
    protected _logger: Logger;
    get netSession(): Session;
    /**
     * The logger. You can pass [electron-log](https://github.com/megahertz/electron-log), [winston](https://github.com/winstonjs/winston) or another logger with the following interface: `{ info(), warn(), error() }`.
     * Set it to `null` if you would like to disable a logging feature.
     * 日志,类型有:info、warn、error
     */
    get logger(): Logger | null;
    set logger(value: Logger | null);
    /**
     * For type safety you can use signals, e.g. 
    为了类型安全,可以使用signals。
     例如:
      `autoUpdater.signals.updateDownloaded(() => {})` instead of `autoUpdater.on('update-available', () => {})`
     */
    readonly signals: UpdaterSignal;
    private _appUpdateConfigPath;
    /**
     * test only
     * @private
     */
    set updateConfigPath(value: string | null);
    private clientPromise;
    protected readonly stagingUserIdPromise: Lazy<string>;
    private checkForUpdatesPromise;
    protected readonly app: AppAdapter;
    protected updateInfoAndProvider: UpdateInfoAndProvider | null;
    protected constructor(
        options: AllPublishOptions | null | undefined,
        app?: AppAdapter
    );
    /**
     * 获取当前更新的url
     */
    getFeedURL(): string | null | undefined;
    /**
     * Configure update provider. If value is `string`, [GenericServerOptions](/configuration/publish#genericserveroptions) will be set with value as `url`.
     * @param options If you want to override configuration in the `app-update.yml`.
     *
     * 配置更新提供者。通过提供url
     * @param options 如果你想覆盖' app-update.yml '中的配置。
     */
    setFeedURL(options: PublishConfiguration | AllPublishOptions | string): void;
    /**
     * 检查服务其是否有更新
     */
    checkForUpdates(): Promise<UpdateCheckResult>;
    isUpdaterActive(): boolean;
    /**
     *
     * @param downloadNotification 询问服务器是否有更新,下载并通知更新是否可用
     */
    checkForUpdatesAndNotify(
        downloadNotification?: DownloadNotification
    ): Promise<UpdateCheckResult | null>;
    private static formatDownloadNotification;
    private isStagingMatch;
    private computeFinalHeaders;
    private isUpdateAvailable;
    protected getUpdateInfoAndProvider(): Promise<UpdateInfoAndProvider>;
    private createProviderRuntimeOptions;
    private doCheckForUpdates;
    protected onUpdateAvailable(updateInfo: UpdateInfo): void;
    /**
     *
     * 作用:开始下载更新包
     *
     * 如果将`autoDownload`选项设置为false,就可以使用这个方法。
     *
     * @returns {Promise<string>} Path to downloaded file.
     */
    downloadUpdate(cancellationToken?: CancellationToken): Promise<any>;
    protected dispatchError(e: Error): void;
    protected dispatchUpdateDownloaded(event: UpdateDownloadedEvent): void;
    protected abstract doDownloadUpdate(
        downloadUpdateOptions: DownloadUpdateOptions
    ): Promise<Array<string>>;
    /**
     * 作用:下载后重新启动应用程序并安装更新。
     *只有在' update- downloads '被触发后才会调用。
     *
     * 注意:如果在update-downloaded钩子中,让用户选择是否更新应用,选择不更新,那就是没有执行autoUpdater.quitAndInstall()方法。
     * 虽然应用没有更新,但是当第二次打开应用的时候,应用检测到本地有更新包,他就会直接更新,最后不会重启更新后的应用。
     *
     * 为了解决这个问题,需要设置`autoInstallOnAppQuit`为false。关闭应用自动更新。
     *
     * **Note:** ' autoUpdater.quitAndInstall() '将首先关闭所有的应用程序窗口,然后只在' app '上发出' before-quit '事件。
     *这与正常的退出事件序列不同。
     *
     * @param isSilent 仅Windows以静默模式运行安装程序。默认为false。
     * @param isForceRunAfter 即使无提示安装也可以在完成后运行应用程序。不适用于macOS。忽略是否isSilent设置为false。
     */
    abstract quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void;
    private loadUpdateConfig;
    private computeRequestHeaders;
    private getOrCreateStagingUserId;
    private getOrCreateDownloadHelper;
    protected executeDownload(
        taskOptions: DownloadExecutorTask
    ): Promise<Array<string>>;
}

最后,希望大家一定要点赞三连。

更多文章都在我的blog地址