Jan 31, 2021
Electron 生命周期事件
常见软件有 ready
close
closed
这些事件,但 electron 的事件也非常的多,如果想要更加深入的了解 electron 整个生命周期的流程,需要对应用里面的生命周期,窗口的生命周期以及页面内的生命周期的时机有一个清晰的理解。
一图胜千言:
一、设计启动退出的事件介绍
这里把这些事件分成三部分,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 | 1 HUP (hang up) |
4. 具体的退出例子
- 正常命令行启动,通过
Cmd+q
退出
1 | ==> app-event: will-finish-launching <=== |
==> window-event: closed <===
这个事件最后才发送是因为 closed 完全是异步的,closed 不会因为没有执行完就阻塞,应用会把所有异步执行完后才退出进程。
可以通过流程图更加清晰的表达:
【流程图】
- 正常启动,通过
ctrl+c
退出
1 | ... |
启动后,所有事件正常,但退出通过 ctrl+c
中断应用, electron 就只发出了 before-quit
事件。试试其它方法:
SIGHUP
和正常的退出流程一致SIGINT
效果同上,也是ctrl+c
发出的信号SIGQUIT
会导致整个应用完全没有任何反应就退出SIGABRT
效果同上SIGKILL
效果同上SIGSEGV
效果同上,也是process.crash()
发出的信号SIGTERM
和正常的退出流程大概一致,也是app.quit()
的退出方式
- 正常启动,通过
app.quit()
退出,这里发现通过这种方式退出,有一些不太一样的地方:
1 | ... |
这里多了一个 unload 事件,也可以看出,其实整个应用通过 app.quit
退出才是正常的退出流程, cmd+q
也会导致部分流程会被忽略退出。然后使用 app.reluanch()
也是和 cmd+q
类似,不会触发 unload
事件。
- 正常启动,通过 beforeunload 中断退出
通过 beforeunload 中调用 event.returnValue = false
中断应用的退出和窗口的关闭:
1 | window.onbeforeunload = (event: Event) => { |
通过 cmd+q
会重复调用下面三个事件:
1 | ==> app-event: before-quit <=== |
如果是直接关闭窗口:
1 | ==> window-event: close <=== |
- 正常启动,通过 BrowserWindow
close
事件中断,就只会触发close
事件:
1 | ==> window-event: close <=== |
BrowserWindow
closed
事件是没有 event 的。
- 正常启动,通过 app
before-quit
中断,直接按cmd+q
是不会关闭窗口。
1 | ==> app-event: before-quit <=== |
通过
ctrl+c
或者上面的一些 SIG 信号关闭,是会忽视这个中断关闭的操作。
- 正常启动,通过 app
will-quit
中断,直接按cmd+q
会关闭所有窗口,但程序还是激活状态。
1 | ==> app-event: before-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 信息:
1 | ==> app-event: did-become-active <=== |
5. 通过拖拽到 dock 或者 tray 启动
通过 dock 拖拽启动,需要在 info.plist
中声明支持的文件类型,比如 electronBuilder 可以通过 extendInfo
字段中,声明 CFBundleDocumentTypes
注册支持的类型。
触发的是事件 open-file
。