【Electron】酷家樂客戶端開發實踐分享 — 進程通訊

做者:鍾離,酷家樂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

渲染進程: 使用ipcRendererremote模塊進行通訊

webview: 通常會禁用webview的node集成,而後使用preload的方式拿到ipcRenderer來作進程通訊。

// preload.js

const electron = require('electron');

const { ipcRenderer } = electron;

// 把ipcRenderer掛載到window上,webview內部的js能夠拿到這個模塊
window.ElectronIpcRenderer = ipcRenderer;

複製代碼

ipcRenderer/ipcMain VS remote

主進程和渲染進程通訊方式,擰出來單獨說一下。先來看一個簡單例子的:

點擊建立按鈕,建立一個新的窗口。點擊關閉按鈕,關掉這個新窗口。

ipc VS remote

左側代碼使用ipcRenderer/ipcMain進行通訊,右側代碼使用remote進行通訊。實現的功能都是同樣的。從這個例子中能夠發現:

  1. 使用ipcMain/ipcRenderer通訊,業務邏輯同時存在於主進程和渲染進程的代碼中。同時爲了通訊,會產生很是多的event & event handler
  2. 使用remote通訊,渲染進程直接獲取主進程模塊。並且,使用remote通訊不須要使用事件和回調函數,寫出來的代碼清晰直觀。

主進程能夠視做爲模塊提供者,而渲染進程是模塊的消費者,渲染進程經過remote來獲取主進程的模塊,實現業務邏輯。這樣作有如下好處:

  1. 主進程/渲染進程代碼解耦,職責分明,提高可維護性
  2. 業務邏輯內聚在渲染進程
  3. 減小主進程/渲染進程冗餘無用的代碼

具體實現

介紹了一下前置知識,如今來看看不一樣狀況下,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
});

複製代碼

webview與渲染進程通訊

內嵌的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

相關文章
相關標籤/搜索