背景
由于某個Electron應用,需要主程序、渲染程序、webview之間能夠互相通訊。
不過因為Electron僅提供了主程序與渲染程序的通訊,沒有渲染程序之間或渲染程序與webview之間通訊的辦法,是以隻能尋找其他方案來解決。
研究一:ipcMain/ipcRenderer
Electron主程序與渲染程序的通訊,就是用ipcMain/ipcRenderer這兩個對象。
// 在主程序中.
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')
View Code
不過隻能ipcRenderer主動發送消息,ipcMain無法主動發送消息給ipcRenderer。
主程序如何主動發送消息給渲染程序?
如果渲染程序的視窗是用BrowserWindow打開的,那麼可以通過webContents.send主動向視窗發送消息。
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的邏輯放在主程序裡,所有執行個體都在主程序裡維護,那麼主動發消息的問題也就解決了。
渲染程序之間如何進行消息通訊?
Electron雖然沒有提供渲染程序之間的通訊,但可以通過主程序中轉來達到這個目的。
步驟:
1、ipcRenderer.send消息到主程序。
2、主程序接收到消息,再通過維護的BrowserWindow執行個體,輪詢webContents.send給各個視窗。
3、渲染程序觸發訂閱主程序事件。
渲染程序與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++插件實作思路:
1、在插件裡定義兩個方法,一個listen(訂閱事件),一個trigger(釋出事件)。
2、在listen裡,将訂閱事件的上下文(Local<Context>)、事件名稱、回調儲存下來。
3、在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實作。
本文為原創文章,轉載請保留原出處,友善溯源,如有錯誤地方,謝謝指正。
本文位址 :http://www.cnblogs.com/lovesong/p/11180336.html