研究Electron主進程、渲染進程、webview之間的通訊


背景

由於某個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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM