天天看點

Electron 生命周期介紹一、應用啟動退出的事件介紹2. BrowserWindow 事件介紹3. Renderer 程序中的 Web 事件介紹二、退出場景三、啟動場景四、其它

常見應用都有啟動、激活、關閉這些事件,Electron 作為一個跨平台的 GUI 架構,為了兼顧不同的場景,需要的事件就會更多。

如果想要更深入的了解 Electron 整個生命周期的流程,需要對應用生命周期,視窗的生命周期以及頁面内的生命周期的時機有一個清晰的了解。

一圖勝千言:

Electron 生命周期介紹一、應用啟動退出的事件介紹2. BrowserWindow 事件介紹3. Renderer 程式中的 Web 事件介紹二、退出場景三、啟動場景四、其它

一、應用啟動退出的事件介紹

這裡把這些事件分成三部分,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. 具體的退出例子

  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 完全是異步的,被觸發後就啥都不管了。使用 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 信号關閉,是會忽視這個中斷關閉的操作。

綜上所述,總結為一個圖:

Electron 生命周期介紹一、應用啟動退出的事件介紹2. BrowserWindow 事件介紹3. Renderer 程式中的 Web 事件介紹二、退出場景三、啟動場景四、其它

三、啟動場景

軟體常見的啟動有很多,比如通過 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