常見應用都有啟動、激活、關閉這些事件,Electron 作為一個跨平台的 GUI 架構,為了兼顧不同的場景,需要的事件就會更多。
如果想要更深入的了解 Electron 整個生命周期的流程,需要對應用生命周期,視窗的生命周期以及頁面内的生命周期的時機有一個清晰的了解。
一圖勝千言:

一、應用啟動退出的事件介紹
這裡把這些事件分成三部分,App 事件、BrowserWindow 事件以及 Renderer 程序中的 Web 事件。這些都是對官方文檔的整理和實際的經驗總結的:
1. App 事件介紹
事件:will-finish-launching
傳回:event: Event
在 Windows/Linux 中與事件 ready 時機相同,在 macOS 中相當于 NSApplication 的 applicationWillFinishLaunching 通知,也就是在 NSApplication 開始初始化,預設的通知中心立即發起這個事件,是以一定比 ready 事件早。
用途:常用于 open-file 和 open-url 監聽器,并啟動崩潰報告和自動更新。
事件:ready
傳回:event: Event, launchInfo: Record<string, any>
事件:open-file [macOS]
應在 ready 之前對 open-file 進行監聽。如果自己接管檔案的打開,應該 event.preventDefault()
觸發條件:
應用已經打開,并且通過擴充名或者 macOS 指令行中的 open 指令打開檔案的時候,觸發
拖放一個檔案到 Dock 但應用還沒有運作的時候觸發
Windows 電腦中,需要通過主程序的 process.argv 進行解析
事件:open-url [macOS]
傳回:event: Event, url: string
事件 open-url 是系統通過 Electron 應用打開 url 時觸發,如果想要自己接管打開url,應該調用event.preventDefault() 。并且要在 info.plist 中定義 url scheme,如果是 Electron Builder 打包的,可以找到 extendInfo 配置,能省去很多麻煩。
原話是這麼說的:Your application's Info.plist file must define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.
// main.ts
// for electron-test://abc?query1=value1
app.setAsDefaultProtocolClient('electron-test');
app.on('will-finish-launching', (event: Event) => {
log(`==> app-event: will-finish-launching <===`);
app.on('open-url', (event: Event, url: string) => {
log(`==> app-event: open-url <===`, url);
});
});
這樣在實作後,比如在浏覽器中輸入 electron-test://abc?query1=value1 位址就可以打開 Electron 應用,并且 open-url 就可以捕獲到這個路徑資訊。
事件:activate [macOS]
傳回:event: Event, hasVisibleWindows: boolean
事件 activate 隻會在【首次啟動應用程式】、【在程式已經運作後再次打開程式】或【單擊應用程式的塢站或工作列圖示時】重新激活它。如果是使用 Cmd+Tab 切換,是不會激活的,這個時候需要 did-become-active 。
這裡的再次打開程式是,macOS 預設是讓應用單例模式,如果嘗試運作另外一個執行個體,就會 activate 已經運作的執行個體。
事件:did-become-active
事件 did-become-active 則會在切換到這個應用的時候觸發,比如沒有視窗的應用或者程式第一次啟動。
事件:session-created
傳回:session: Session
建立一個 default session,常用于網絡請求環境的隔離。
事件:web-contents-created
傳回:event Event, window: BrowserWindow
建立 webContents 的上下文環境就緒。有可能會被初始化多次
事件:browser-window-created
傳回:event: Event, window: BrowserWindow
建立一個視窗,都是依次以 session-created , web-contents-created , browser-window-created 建立。
事件:second-instance
傳回:event: Event, argv: string[], workingDirectory: string
在 macOS 中大多數情況啟動應用程式是單例模式,當再啟動的時候,如果調用了 app.requestSingleInstanceLock() 就會在原來運作中的應用觸發這個事件,新的應用可以控制是否退出。
事件:window-all-closed
傳回:無
當應用所有視窗關閉後觸發,其它情況,比如 app.quit 、 cmd+q 或者菜單的退出,或者任何其它方式的退出軟體都不會觸發。
預設不監聽這個事件,關閉所有視窗自動退出應用。一旦監聽了這個事件,那麼所有視窗關閉後不會退出應用,需要開發者自己控制。
事件:before-quit
中斷:可以
任何正常嘗試關閉應用的行為都會馬上觸發。
Electron 文檔中說 autoUpdater.quitAndInstall() 會關閉所有的視窗,然後調用 app.quit() 。
事件:will-quit
在不監聽 window-all-closed 時,所有的視窗都關閉後觸發 will-quit。如果 window-all-closed 被監聽了, will-quit 被觸發了 window-all-closed 也不會被觸發。
事件:quit
傳回:event: Event, exitCode: number
在 Windows 系統中,如果應用程式因系統關機/重新開機或使用者登出而關閉,那麼 before-quit 和 quit 事件不會被觸發。
2. BrowserWindow 事件介紹
事件:close
要比 Web 中的 DOM 事件 unload 和 beforeunload 要早,在一般情況下,都是通過 beforeunload 處理視窗關閉的事情:
window.onbeforeunload = (e) => { console.log('I do not want to be closed') e.returnValue = false // equivalent to
return false
but not recommended }
事件: closed
因為這是個異步事件,是以回調的方法無法影響 BrowserWindow 的生命周期。
事件: ready-to-show
這個視窗屬性如果是 show:true ,也會在沒顯示的時候就觸發事件。
3. Renderer 程序中的 Web 事件介紹
事件:window: load
在視窗開始加載的時候觸發,添加監聽有兩種方法,下面的 unlaod 和 beforeunload 也是一樣:
window.onload = (event: Event) => {
console.log(event.type) // output: load
}
window.addEventListener('load', (event: Event) => {
console.log(event.type) // output: load
})
事件:window: unload
unload 事件不會被中斷。
一般情況下,Electron 退出是來不及發出 unload 事件的,隻有關閉視窗和重載視窗會發出。
事件:window: beforeunload
- 傳回非 undefined 就會中斷主程序 BrowserWindow 的 close 事件
- 在視窗重載的時候,也會被觸發
事件:document: DOMContentLoaded
等所有 DOM 準備好後觸發。
二、退出場景
1. 正常退出
Cmd+q 或者菜單中的退出按鈕,Windows 中 Alt+F4
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. 具體的退出例子
- 正常指令行啟動,通過 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 完全是異步的,被觸發後就啥都不管了。使用 app.reluanch() 也是和 cmd+q 一樣,不會觸發 unload 事件。
2.正常啟動,通過 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() 的退出方式
3.正常啟動,通過 app.exit() 退出
通過這種方式退出,與 process.exit() 一樣,隻會觸發 quit 事件。
==> html-event: DOMContentLoaded <===
==> window-event: ready-to-show <===
==> app-event: quit 0 <===
4.正常啟動,通過 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 <===
5.正常啟動,通過 BrowserWindow close 事件中斷,就隻會觸發 close 事件:
==> window-event: close <===
==> window-event: close <===
==> window-event: close <===
BrowserWindow closed 事件是沒有 event 的。
6.正常啟動,通過 app before-quit 中斷,直接按 cmd+q 是不會關閉視窗。
==> app-event: before-quit <===
==> app-event: before-quit <===
==> app-event: before-quit <===
通過 Ctrl+c 或者上面的一些 SIG 信号關閉,是會忽視這個中斷關閉的操作。
7.正常啟動,通過 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 。
四、其它
Q:為什麼在前面的試驗中, web-contents-created 會被觸發兩次?
A:因為在源碼中調用 mainWindow.webContents.openDevTools() 打開了 DevTools,是以會建立另外一個 WebContents 環境加載 DevTools。就算是彈出獨立的 DevTools 視窗,也會建立一個 WebContents。
另外在 Windows, web-contents-created 不會觸發兩次,但 ready-to-show 會觸發兩次。
Q:Windows 中事件 before-quit 、 will-quit 和 quit 在使用者關閉重新開機系統或者登出目前使用者的時候不會觸發,那該怎麼處理退出時的任務?
A:如果是一定要通知到,需要引入第三方庫,比如 edge.js 。一般最好就是對退出的場景做一些臨時存儲,這樣再次打開的時候,做後續的處理。
Q:BrowserWindow 執行個體調用 browserWindow.destroy() 會導緻什麼情況?
A:隻會通知 closed 事件,其它比如 unload 、 beforeunload 、BrowserWindow 的事件 close 都不會觸發。
本文使用的代碼示例在這裡
https://github.com/yantze/electron-lifecycle-example。