Electron 流程模型

2023-02-16 17:14 更新

Electron 繼承了來自 Chromium 的多進程架構(gòu),這使得此框架在架構(gòu)上非常相似于一個現(xiàn)代的網(wǎng)頁瀏覽器。 本指南將對 教程 中應(yīng)用的概念進行拓展。

為什么不是一個單一的進程??

網(wǎng)頁瀏覽器是個極其復(fù)雜的應(yīng)用程序。 除了顯示網(wǎng)頁內(nèi)容的主要能力之外,他們還有許多次要的職責(zé),例如:管理眾多窗口 ( 或 標(biāo)簽頁 ) 和加載第三方擴展。

在早期,瀏覽器通常使用單個進程來處理所有這些功能。 雖然這種模式意味著您打開每個標(biāo)簽頁的開銷較少,但也同時意味著一個網(wǎng)站的崩潰或無響應(yīng)會影響到整個瀏覽器。

多進程模型?

為了解決這個問題,Chrome 團隊決定讓每個標(biāo)簽頁在自己的進程中渲染, 從而限制了一個網(wǎng)頁上的有誤或惡意代碼可能導(dǎo)致的對整個應(yīng)用程序造成的傷害。 然后用單個瀏覽器進程控制這些標(biāo)簽頁進程,以及整個應(yīng)用程序的生命周期。 下方來自 Chrome 漫畫 的圖表可視化了此模型:


Electron 應(yīng)用程序的結(jié)構(gòu)非常相似。 作為應(yīng)用開發(fā)者,你將控制兩種類型的進程:主進程 和 渲染器進程。 這類似于上文所述的 Chrome 的瀏覽器和渲染器進程。

主進程

每個 Electron 應(yīng)用都有一個單一的主進程,作為應(yīng)用程序的入口點。 主進程在 Node.js 環(huán)境中運行,這意味著它具有 require 模塊和使用所有 Node.js API 的能力。

窗口管理

主進程的主要目的是使用 ?BrowserWindow? 模塊創(chuàng)建和管理應(yīng)用程序窗口。

?BrowserWindow? 類的每個實例創(chuàng)建一個應(yīng)用程序窗口,且在單獨的渲染器進程中加載一個網(wǎng)頁。 您可從主進程用 window 的 ?webContent? 對象與網(wǎng)頁內(nèi)容進行交互。

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

const contents = win.webContents
console.log(contents)

注意:渲染器進程也是為 web 嵌入 而被創(chuàng)建的,例如 ?BrowserView? 模塊。 嵌入式網(wǎng)頁內(nèi)容也可訪問 ?webContents? 對象。

由于 BrowserWindow 模塊是一個 EventEmitter, 所以您也可以為各種用戶事件 ( 例如,最小化 或 最大化您的窗口 ) 添加處理程序。

當(dāng)一個 BrowserWindow 實例被銷毀時,與其相應(yīng)的渲染器進程也會被終止。

應(yīng)用程序生命周期

主進程還能通過 Electron 的 ?app? 模塊來控制您應(yīng)用程序的生命周期。 該模塊提供了一整套的事件和方法,可以讓您用來添加自定義的應(yīng)用程序行為 (例如:以編程方式退出您的應(yīng)用程序、修改應(yīng)用程序塢,或顯示一個關(guān)于面板) 。

這是一個實際的例子,這個app來源于 快速入門,用 app API 創(chuàng)建了一個更原生的應(yīng)用程序窗口體驗。

// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

原生 API?

為了使 Electron 的功能不僅僅限于對網(wǎng)頁內(nèi)容的封裝,主進程也添加了自定義的 API 來與用戶的作業(yè)系統(tǒng)進行交互。 Electron 有著多種控制原生桌面功能的模塊,例如菜單、對話框以及托盤圖標(biāo)。

關(guān)于 Electron 主進程模塊的完整列表,請參閱我們的 API 文檔。

渲染器進程

每個 Electron 應(yīng)用都會為每個打開的 BrowserWindow ( 與每個網(wǎng)頁嵌入 ) 生成一個單獨的渲染器進程。 洽如其名,渲染器負責(zé) 渲染 網(wǎng)頁內(nèi)容。 所以實際上,運行于渲染器進程中的代碼是須遵照網(wǎng)頁標(biāo)準的 (至少就目前使用的 Chromium 而言是如此) 。

因此,一個瀏覽器窗口中的所有的用戶界面和應(yīng)用功能,都應(yīng)與您在網(wǎng)頁開發(fā)上使用相同的工具和規(guī)范來進行攥寫。

雖然解釋每一個網(wǎng)頁規(guī)范超出了本指南的范圍,但您最起碼要知道的是:

  • 以一個 HTML 文件作為渲染器進程的入口點。
  • 使用層疊樣式表 (Cascading Style Sheets, CSS) 對 UI 添加樣式。
  • 通過 ?<script>? 元素可添加可執(zhí)行的 JavaScript 代碼。

此外,這也意味著渲染器無權(quán)直接訪問 require 或其他 Node.js API。 為了在渲染器中直接包含 NPM 模塊,您必須使用與在 web 開發(fā)時相同的打包工具 (例如 webpack 或 parcel)

:::警告

為了方便開發(fā),可以用完整的 Node.js 環(huán)境生成渲染器進程。 在歷史上,這是默認的,但由于安全原因,這一功能已被禁用。

:::

此刻,您或許會好奇:既然這些特性只能由主進程訪問,那渲染器進程用戶界面怎樣才能與 Node.js 和 Electron 的原生桌面功能進行交互。 而事實上,確實沒有直接導(dǎo)入 Electron 內(nèi)容腳本的方法。

Preload 腳本

預(yù)加載(preload)腳本包含了那些執(zhí)行于渲染器進程中,且先于網(wǎng)頁內(nèi)容開始加載的代碼 。 這些腳本雖運行于渲染器的環(huán)境中,卻因能訪問 Node.js API 而擁有了更多的權(quán)限。

預(yù)加載腳本可以在 BrowserWindow 構(gòu)造方法中的 webPreferences 選項里被附加到主進程。

const { BrowserWindow } = require('electron')
//...
const win = new BrowserWindow({
  webPreferences: {
    preload: 'path/to/preload.js',
  },
})
//...

因為預(yù)加載腳本與瀏覽器共享同一個全局 Window 接口,并且可以訪問 Node.js API,所以它通過在全局 window 中暴露任意 API 來增強渲染器,以便你的網(wǎng)頁內(nèi)容使用。

雖然預(yù)加載腳本與其所附著的渲染器在共享著一個全局 window 對象,但您并不能從中直接附加任何變動到 window 之上,因為 上下文隔離 是默認的。

window.myAPI = {
  desktop: true,
}
console.log(window.myAPI)
// => undefined

語境隔離(Context Isolation)意味著預(yù)加載腳本與渲染器的主要運行環(huán)境是隔離開來的,以避免泄漏任何具特權(quán)的 API 到您的網(wǎng)頁內(nèi)容代碼中。

取而代之,我們將使用 ?contextBridge? 模塊來安全地實現(xiàn)交互:

const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  desktop: true,
})
console.log(window.myAPI)
// => { desktop: true }

此功能對兩個主要目的來說非常有用:

  • 通過暴露 ?ipcRenderer? 幫手模塊于渲染器中,您可以使用 進程間通訊 ( inter-process communication, IPC ) 來從渲染器觸發(fā)主進程任務(wù) ( 反之亦然 ) 。
  • 如果您正在為遠程 URL 上托管的現(xiàn)有 web 應(yīng)用開發(fā) Electron 封裝,則您可在渲染器的 ?window? 全局變量上添加自定義的屬性,好在 web 客戶端用上僅適用于桌面應(yīng)用的設(shè)計邏輯 。

實用程序

每個 Electron 應(yīng)用程序都可以使用 UtilityProcess API 從主進程生成多個子進程。實用程序進程在 Node.js 環(huán)境中運行,這意味著它能夠要求模塊并使用所有 Node.js API。實用進程可用于托管例如:不受信任的服務(wù)、CPU 密集型任務(wù)或容易崩潰的組件,這些組件以前托管在主進程或使用 Node.js child_process.fork API 生成的進程中。實用程序進程與 Node.js child_process 模塊生成的進程之間的主要區(qū)別在于,實用程序進程可以使用 MessagePorts 與渲染器進程建立通信通道。當(dāng)需要從主進程中 fork 一個子進程時,Electron 應(yīng)用程序總是更喜歡 UtilityProcess API 而不是 Node.js child_process.fork API。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號