常见软件有 ready close closed 这些事件,但 electron 的事件也非常的多,如果想要更加深入的了解 electron 整个生命周期的流程,需要对应用里面的生命周期,窗口的生命周期以及页面内的生命周期的时机有一个清晰的理解。

一图胜千言:
electron-lifecycle

一、设计启动退出的事件介绍

这里把这些事件分成三部分,App 事件、BrowserWindow 事件以及 Renderer 进程中的 Web 事件:

1. App 事件

  • on('will-finish-launching', (event: Event) => {})
  • on('ready', (event: Event, launchInfo: Record<string, any>) => {})

  • on('open-file', (event: Event, path: string) => {}) 【macOS】

应该在 ready 之前对 open-file 进行监听。如果想要自己接管文件的打开,应该 event.preventDefault()
触发条件:

  • 应用已经打开,并且通过扩展名或者 open 命令打开文件的时候,触发
  • 拖放一个文件到 Dock 但应用还没有运行的时候触发

Windows 电脑中,需要通过主进程的 process.argv 进行解析

  • on('open-url', (event: Event, url: string) => {})【macOS】

事件 open-url  是处理通过系统通过 electron 应用打开 url 时触发,如果想要自己接管打开 url,应该 event.preventDefault() 。并且要在 info.plist 中定义 url scheme,原话是这么说的:Your application's Info.plist file must define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.

  • on('activate', (event: Event, hasVisibleWindows: boolean) => {}) 【macOS】

事件 activate  只会在【首次启动应用程序】、【尝试在应用程序已运行时】或【单击应用程序的坞站或任务栏图标时】重新激活它。

  • on('did-become-active', (event: Event) => {}) 【macOS】

事件 did-become-active 则会在切换到这个应用的时候触发,比如没有窗口的应用或者程序第一次启动。

  • on('session-created', (session: Session) => {})
  • on('web-contents-created', (event: Event, window: BrowserWindow) => {})
  • on('browser-window-created', (event: Event, window: BrowserWindow) => {})

创建一个窗口,都是依次以 session-created , web-contents-created , browser-window-created  创建。但不知道为什么在 app ready  事件后,又触发了事件 web-contents-created 。

  • on('browser-window-focus', (event: Event, window: BrowserWindow) => {})
  • on('second-instance', (event: Event, argv: string[], workingDirectory: string) => {})

  • on('window-all-closed', ()=>{})

这个事件只有一个窗口一个窗口的关闭后,才会触发,其它情况,比如 app.quit 、 cmd+q  或者菜单的退出,或者任何其它方式的退出软件都不会触发。

  • on('before-quit', (event: Event) => {})

如果应用关闭是被 autoUpdater.quitAndInstall()  发起的,那么在所有窗口触发 close  之后 才会触发 before-quit  并关闭所有窗口。

  • on('quit', (event: Event, exitCode: number) => {})

在 Windows 系统中,如果应用程序因系统关机/重启或用户注销而关闭,那么 before-quit  和 quit  事件不会被触发。

2. BrowserWindow 事件

  • on('close', (event: Event) =>{})
  • on('closed', () => {})
  • on('ready-to-show', () => {})  还没有显示的时候就发起

3. Renderer 进程

  • window.onunload
  • window.onbeforeunload  返回非 undefined 就会中断 主进程 close 事件
  • document.addEventListener('DOMContentLoaded')
  • document.addEventListener('visibilitychange')

二、退出场景

1. 正常退出

  • Cmd+q  或者菜单中的退出按钮。
  • app.quit()
  • autoupdater.quitAndInstall()
  • app.reluanch()

2. 异常退出

比如常见的在主进程调用 process.crash()

3. SIG 信号退出

我们常见的命令行退出软件的方式有 ctrl+c ,命令行给进程发送了 SIGKILL  信号,其实还有其它常见关闭进程的方式,可以通过 kill  对进程发送信号,比如 kill -s KILL 24567  或者 kill -9 24567 :

1       HUP (hang up)
2       INT (interrupt)
3       QUIT (quit)
6       ABRT (abort)
9       KILL (non-catchable, non-ignorable kill)
14      ALRM (alarm clock)
15      TERM (software termination signal)

4. 具体的退出例子

  1. 正常命令行启动,通过 Cmd+q  退出
==> app-event: will-finish-launching <===
==> app-event: session-created <===
==> app-event: web-contents-created <===
==> app-event: browser-window-created <===
==> app-event: ready <===
==> app-event: did-become-active <===
==> app-event: web-contents-created <===
==> html-event: DOMContentLoaded <===
==> html-event: load <===
==> window-event: ready-to-show <===

==> app-event: before-quit <===
==> window-event: close <===
==> html-event: beforeunload <===
==> app-event: will-quit <===
==> app-event: quit <===
==> window-event: closed <===

==> window-event: closed <===  这个事件最后才发送是因为 closed 完全是异步的,closed 不会因为没有执行完就阻塞,应用会把所有异步执行完后才退出进程。

可以通过流程图更加清晰的表达:
【流程图】

  1. 正常启动,通过 ctrl+c  退出
...
==> window-event: ready-to-show <===

^C==> app-event: before-quit <===

启动后,所有事件正常,但退出通过 ctrl+c  中断应用, electron 就只发出了 before-quit  事件。试试其它方法:

  • SIGHUP  和正常的退出流程一致
  • SIGINT  效果同上,也是 ctrl+c  发出的信号
  • SIGQUIT 会导致整个应用完全没有任何反应就退出
  • SIGABRT  效果同上
  • SIGKILL  效果同上
  • SIGSEGV 效果同上,也是 process.crash()  发出的信号
  • SIGTERM  和正常的退出流程大概一致,也是 app.quit()  的退出方式

  1. 正常启动,通过 app.quit()  退出,这里发现通过这种方式退出,有一些不太一样的地方:
...
==> window-event: ready-to-show <===

==> app-event: before-quit <===
==> window-event: close <===
==> html-event: beforeunload <===
==> html-event: unload <===
==> app-event: will-quit <===
==> app-event: quit <===
==> window-event: closed <===

这里多了一个 unload 事件,也可以看出,其实整个应用通过 app.quit  退出才是正常的退出流程, cmd+q  也会导致部分流程会被忽略退出。然后使用 app.reluanch()  也是和 cmd+q  类似,不会触发 unload  事件。

  1. 正常启动,通过 beforeunload 中断退出

通过 beforeunload 中调用 event.returnValue = false  中断应用的退出和窗口的关闭:

window.onbeforeunload = (event: Event) => {
  log("beforeunload");
  event.returnValue = true;
};

通过 cmd+q  会重复调用下面三个事件:

==> app-event: before-quit <===
==> window-event: close <===
==> html-event: beforeunload <===

如果是直接关闭窗口:

==> window-event: close <===
==> html-event: beforeunload <===
  1. 正常启动,通过 BrowserWindow close  事件中断,就只会触发 close  事件:
==> window-event: close <===
==> window-event: close <===
==> window-event: close <===

BrowserWindow closed  事件是没有 event 的。

  1. 正常启动,通过 app before-quit  中断,直接按 cmd+q  是不会关闭窗口。
==> app-event: before-quit <===
==> app-event: before-quit <===
==> app-event: before-quit <===

通过 ctrl+c 或者上面的一些 SIG 信号关闭,是会忽视这个中断关闭的操作。

  1. 正常启动,通过 app will-quit  中断,直接按 cmd+q  会关闭所有窗口,但程序还是激活状态。
==> app-event: before-quit <===
==> app-event: will-quit <===
==> app-event: before-quit <===
==> app-event: will-quit <===
==> app-event: before-quit <===
==> app-event: will-quit <===

通过 ctrl+c 或者上面的一些 SIG 信号关闭,是会忽视这个中断关闭的操作。

三、启动场景

软件常见的启动有很多,比如通过 open  或者 Windows 的 start  启动,或者通过 url scheme 然后系统启动,或者拖动文档到 dock 或者 tray 启动。下面来说说启动软件后,软件参数和环境的路径。

1. 普通启动

一般双击软件启动,会经过 will-finish-launching  和 ready ,然后正常进入应用界面。

2. 单例模式下的应用启动

在应用启动的时候,检测 app.requestSingleInstanceLock()  是否是单例模式,如果是就退出,并且发送一个事件给 second-instance ,从这里再获取退出的应用的 argv 和 cwd。

其实这里检测是否是单例模式的方法是,看看 app.getPath('userData')  下是否有 lock 文件。

3. 通过命令行启动

命令行执行 /path/to/app --arg1 value1 --arg2 value2 document/path ,会在应用启动的时候,通过 process.argv  和 process.cwd

4. 通过 url scheme 启动

通过 app.setAsDefaultProtocolClient('electron-test')  注册 url scheme,然后在 app 事件 open-url  的 url 参数获取 url 信息:

==> app-event: did-become-active <===
==> app-event: open-url <=== electron-test://happy?abc=eee#ii=aa

5. 通过拖拽到 dock 或者 tray 启动

通过 dock 拖拽启动,需要在 info.plist 中声明支持的文件类型,比如 electronBuilder 可以通过 extendInfo  字段中,声明 CFBundleDocumentTypes  注册支持的类型。
触发的是事件 open-file 。