背景html
因爲某個Electron應用,須要主進程、渲染進程、webview之間可以互相通信。node
不過由於Electron僅提供了主進程與渲染進程的通信,沒有渲染進程之間或渲染進程與webview之間通信的辦法,因此只能尋找其餘方案來解決。c++
研究一:ipcMain/ipcRendererweb
Electron主進程與渲染進程的通信,就是用ipcMain/ipcRenderer這兩個對象。npm
// 在主進程中. const { ipcMain } = require('electron') ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.reply('asynchronous-reply', 'pong') }) ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.returnValue = 'pong' }) //在渲染器進程 (網頁) 中。 const { ipcRenderer } = require('electron') console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" ipcRenderer.on('asynchronous-reply', (event, arg) => { console.log(arg) // prints "pong" }) ipcRenderer.send('asynchronous-message', 'ping')
不過只能ipcRenderer主動發送消息,ipcMain沒法主動發送消息給ipcRenderer。api
主進程如何主動發送消息給渲染進程?app
若是渲染進程的窗口是用BrowserWindow打開的,那麼能夠經過webContents.send主動向窗口發送消息。electron
let win = new BrowserWindow({ width: 800, height: 600 }) win.loadURL(`file://${__dirname}/index.html`) win.webContents.on('did-finish-load', () => { win.webContents.send('ping', 'whoooooooh!') })
那麼若是想主進程主動向渲染進程發送消息,就能夠將建立BrowserWindow的邏輯放在主進程裏,全部實例都在主進程裏維護,那麼主動發消息的問題也就解決了。socket
渲染進程之間如何進行消息通信?async
Electron雖然沒有提供渲染進程之間的通信,但能夠經過主進程中轉來達到這個目的。
步驟:
一、ipcRenderer.send消息到主進程。
二、主進程接收到消息,再經過維護的BrowserWindow實例,輪詢webContents.send給各個窗口。
三、渲染進程觸發訂閱主進程事件。
渲染進程與webview之間如何通信?
因爲被打開渲染窗口中,會使用到webview標籤(相似iframe)嵌入頁面,因此這裏也須要互相通信。
webview是一個標籤,它有一個ipc-message事件接收渲染進程的消息,以下。
// In embedder page. const webview = document.querySelector('webview') webview.addEventListener('ipc-message', (event) => { console.log(event.channel) // Prints "pong" }) webview.send('ping’) //在訪客頁。 const { ipcRenderer } = require('electron') ipcRenderer.on('ping', () => { ipcRenderer.sendToHost('pong') })
必須明確一點的是,上面代碼中webview監聽ipc-message事件的代碼是寫在渲染進程中的,不是在webview本身頁面代碼裏。這就有一個很尷尬的問題,事件是有了,但webview頁面裏並不知道。
通過幾番嘗試,確實沒法在嵌入頁面接收到事件。
結論
在Electron提供的功能裏,只能作到主進程和渲染進程的互相通訊,webview像個棄子同樣被隔離開了。
研究二:c++插件
上一個方案走不通後,我又想到是否能夠作一個c++插件來實現。
PS:http://nodejs.cn/api/addons.html
c++插件實現思路:
一、在插件裏定義兩個方法,一個listen(訂閱事件),一個trigger(發佈事件)。
二、在listen裏,將訂閱事件的上下文(Local<Context>)、事件名稱、回調保存下來。
三、在trigger裏,遍歷保存的訂閱信息,匹配事件名稱,而後調用訂閱信息中的回調。
這裏關鍵的思想就是,在插件有個全局變量來保存各個進程的訂閱信息,全部的進程都使用同一個實例對象(單例)。
可是在require插件時候,我發現每一個進程都是各自一個實例,不是單例,作不到共享全局變量。
結論
由於require插件的實例不是單例,因此此方案也夭折了。
研究三:socket
在上面方法驗證走不通後,最後選擇socket方式來中轉消息。
PS:https://www.npmjs.com/package/ws
//in main process const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8888 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(data) { wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(data); } }); }); }); //in render process or webview const WebSocket = require('ws'); const busClient = new WebSocket('ws://127.0.0.1:' + busPort + '/'); busClient.on('message', function incoming(data) { console.log(data) }); busClient.send('hello world’);
事件訂閱與發佈就能夠基於上面代碼實現。
//in render process or webview var busEvents = {}; const WebSocket = require('ws'); const busClient = new WebSocket('ws://127.0.0.1:' + busPort + '/'); busClient.on('message', function incoming(data) { data = JSON.parse(data); if(busEvents[data.eventName]){ busEvents[data.eventName].apply(this, data.args); } }); function listen(eventName, func) { busEvents[eventName] = func; } function trigger(eventName, args) { busClient.send(JSON.stringify({ eventName, args })) }
總結
Electron主進程、渲染進程、webview之間的通信,只能經過socket實現。
本文爲原創文章,轉載請保留原出處,方便溯源,若有錯誤地方,謝謝指正。