做者:鍾離,酷家樂PC客戶端負責人html
原文地址:webfe.kujiale.com/electron-ku…前端
酷家樂客戶端:下載地址 www.kujiale.com/activity/13…node
文章背景:在酷家樂客戶端在V12改版成功後,咱們積累了許多的寶貴的經驗和最佳實踐。前端社區裏關於Electron知識相對較少,所以但願將這些內容以系列文章的形式分享出來。web
系列文章:api
Electron中的進程,其實就是計算機中的進程,咱們先來看看什麼是進程通訊瀏覽器
進程間通訊(IPC,Inter-Process Communication),指至少兩個進程或線程間傳送數據或信號的一些技術或方法安全
每一個進程都有本身的一部分獨立的系統資源,彼此是隔離的。爲了能使不一樣的進程互相訪問資源並進行協調工做,纔有了進程間通訊。app
一個Electron應用有一個主進程和多個渲染進程,渲染進程還可能內嵌多個webview。兩兩之間均可能須要進行通訊,狀況仍是比較複雜的。dom
主進程: 使用ipcMain
進行通訊electron
渲染進程: 使用ipcRenderer
和remote
模塊進行通訊
webview: 通常會禁用webview的node集成,而後使用preload的方式拿到ipcRenderer
來作進程通訊。
// preload.js
const electron = require('electron');
const { ipcRenderer } = electron;
// 把ipcRenderer掛載到window上,webview內部的js能夠拿到這個模塊
window.ElectronIpcRenderer = ipcRenderer;
複製代碼
主進程和渲染進程通訊方式,擰出來單獨說一下。先來看一個簡單例子的:
點擊建立按鈕,建立一個新的窗口。點擊關閉按鈕,關掉這個新窗口。
左側代碼使用ipcRenderer/ipcMain
進行通訊,右側代碼使用remote進行通訊。實現的功能都是同樣的。從這個例子中能夠發現:
ipcMain/ipcRenderer
通訊,業務邏輯同時存在於主進程和渲染進程的代碼中。同時爲了通訊,會產生很是多的event & event handler
。主進程能夠視做爲模塊提供者,而渲染進程是模塊的消費者,渲染進程經過remote來獲取主進程的模塊,實現業務邏輯。這樣作有如下好處:
介紹了一下前置知識,如今來看看不一樣狀況下,Electron進程通訊的實現方法。
主進程發消息、渲染進程收消息:主進程使用窗口的webContents
發消息,渲染進程內使用ipcRenderer
收消息
// main.js
const win = new BrowserWindow();
win.load('index.html');
win.webContents.send('hello', {a: 1});
// index.html 中的js
const { ipcRenderer } = require('electron');
ipcRenderer.on('hello', (e, data) => {
console.log(data); // 打印出 {a: 1}
})
複製代碼
渲染進程發消息、主進程收消息: 渲染進程使用ipcRenderer
發消息,主進程使用ipcMain
收消息。
// main.js
const { ipcMain } = require('electron');
ipcMain.on('hello', (e, data) => {
console.log(data); // 打印出 {a: 1}
});
// index.html 中的js
const { ipcRenderer } = require('electron');
ipcRenderer.send('hello', {a: 1});
複製代碼
通常遇到主進程和渲染進程通訊的狀況,大部分都是渲染進程來須要獲取主進程的模塊,此時推薦使用remote
來作通訊。
// main.js
// 主進程無需添加任何代碼
// index.html 中的js,獲取主進程模塊
const { remote } = require('electron');
const {app, BrowserWindow, dialog, ...} = remote;
複製代碼
渲染進程之間也是會頻繁通訊的,具體場景舉例:在設置窗口點擊更換皮膚,須要通知全部窗口進行顏色、背景的更新。
最佳實踐:渲染進程A經過remote
模塊,獲取到須要目標窗口的webContents
對象,而後經過webContents
向目標窗口的發送消息。目標窗口使用ipcRenderer
監聽事件。
const { remote } = require('electron')
const allWindows = remote.BrowserWindow.getAllWindows();
// 窗口A中的邏輯
// 一、第一步,獲取到目標窗口的webContents
// 能夠根據id,title來找到目標窗口,也能夠用其餘辦法
const targetId = 1;
const targetTitle = '目標窗口';
// let targetWindow = allWindows.find(w => w.id === targetId);
let targetWindow = allWindows.find(w => w.title === targetTitle);
// 二、第二步,使用目標窗口的webContents發送消息
targetWindow.webContents.send('theme-change', 'gray');
// 目標窗口內的邏輯,使用ipcRenderer監聽事件
// 窗口收到theme-change事件,改變窗口顏色。不須要關注事件從哪裏發出,只須要關注接收到該事件後作什麼
ipcRenderer.on('theme-change', (e, theme) => {
console.log(theme); // gray
});
複製代碼
還有一種傳統的辦法,不用remote
,改用ipcMain
作通訊,可是會在主進程冗餘不少事件代碼。所以仍是推薦使用remote
,理由同上。小例子:
// mian.js
// 用於事件轉發,沒有實際的邏輯
ipcMain.on('send-event-to-window', (e, id, eventName, ...args) => {
BrowserWindow.getAllWindows()
.find(w = > w.id === id)
.webContents
.send(eventName, ...args);
});
// 窗口A內部,向主進程發事件
const targetId = 1;
ipcRenderer.send('send-event-to-window', id, 'theme-change', 'gray');
// 目標窗口
ipcRenderer.on('theme-change', (e, theme) => {
console.log(theme); // gray
});
複製代碼
內嵌的web頁面運行在客戶端中,也能夠獲取本地化的能力。此時,webview就須要與渲染進程通訊了。
在文章開頭講到了,爲了應用的安全性webview是須要禁用node集成的,經過preload的方式,注入了一個ipcRenderer
並掛載到window上。
webview發消息,渲染進程收消息:webview內部使用ipcRenderer.sendToHost
來發消息。渲染進程獲取到webview的dom元素,監聽dom元素的ipc-message事件接收消息
// 渲染進程拿到webview的dom,接收事件
const webview = document.querySelector('webview')
webview.addEventListener('ipc-message', (event) => {
console.log(event.channel); // hello
});
// webview頁面內,僞裝點了一個按鈕,發送事件
btn.onclick = () => {
window.ElectronIpcRenderer.sendToHost('hello')
}
複製代碼
渲染進程發消息,webview收消息:渲染進程使用webview.send
發消息。webview使用內置的ipcRenderer
收消息。
// webview內部
window.ElectronIpcRenderer.on('event-from-renderer', (e, data) => {
console.log(e, data); // {a: 1}
});
// 渲染進程內部
const webview = document.querySelector('webview')
webview.send('event-from-renderer', {a: 1})
複製代碼
這三種通訊方式是最基礎的,在此之上進行排列組合也是很常見的,這個由開發者自行拓展便可。
舉一個小例子:webview內部觸發更換皮膚功能 -> 通知渲染進程同步更新皮膚 -> 渲染進程收到消息,向其餘渲染進程通訊 -> 同步更新皮膚完成。
歡迎你們在評論區討論,技術交流 & 內推 -> zhongli@qunhemail.com