大概是全網(wǎng)最詳細的Electron ipc 講解(三)——定情信物傳聲筒port
以下文章來源于LinDaiDai ,作者LinDaiDai
文章
《大概是全網(wǎng)最詳細的Electron ipc 講解(一)——主進程與渲染進程的兩情相悅》,
《大概是全網(wǎng)最詳細的Electron ipc 講解(二)——渲染進程與渲染進程的搭橋牽線》
前言
本系列共有以下幾個章節(jié):
主進程與渲染進程的兩情相悅
渲染進程與渲染進程的搭橋牽線
定情信物傳聲筒port
您此次閱讀的是第三章節(jié):定情信物傳聲筒port。
注:以上所有文章都被歸檔到:https://github.com/LinDaiDai/niubility-coding-js 中 ,案例都上傳至:https://github.com/LinDaiDai/electron-ipc-example ,歡迎 Star,感謝 Star。
大綱
在之前的文章中,我們主要介紹了 ipcMain 和 ipcRenderer 是如何實現(xiàn)主進程與渲染進程、以及渲染進程與渲染進程進行通信的。大家不難發(fā)現(xiàn),之前介紹的方式都非常依賴主進程,特別是渲染進程之間的通信,每次都需要主進程這個中間人來傳話,難道就沒有什么更簡單點的方式嗎?
咦,這還真有一個,那就是利用 MessagePort ,通俗易懂的翻譯過來:消息端口...(啪,就你英語好是吧)
大家別急,還不知道是啥玩意的話,讓我來給大家介紹一下。用個小故事來簡單舉個例子哈:
渲染進程一(某先生)和渲染進程二(某女士)通過媒人介紹認識,由于男俊女美且三觀相符,很快兩人就看對眼墜入愛河了,但是奈何工作地點不在一起,不得不異地。這剛認識還處于曖昧期的倆年輕人咋能忍住不聯(lián)系呢。于是互相交換了手機號加上了wx,相親結(jié)束后,每日通過手機互相聯(lián)系,增進感情,不日,便確定關(guān)系,訂婚,結(jié)婚,買房,生娃......停停停,給我回來。
咳咳咳,故事呢,其實到 "每日通過手機互相聯(lián)系" 就結(jié)束了哈,后面自行腦補。在上面這則小故事中,媒人就是主進程,渲染進程一和二在最初,會通過主進程進行一個交換 port 的過程,后續(xù)都通過 port 來進行通信,不再依賴主進程了。
乍一看,是不是覺得這種通信方式比前面介紹的那些靠譜多了,而且還不需要通過主進程中繼的性能開銷。
講完了故事,聊了個大概,讓我們來看看本篇文章大綱吧:
MessagePort 的基礎(chǔ)用法
主進程與渲染進程通信案例分析
渲染進程與渲染進程案例預(yù)告
1. MessagePort 的基礎(chǔ)用法
1.1 如何創(chuàng)建 MessagePort
首先還是得先來看看 MessagePort 的基礎(chǔ)用法。MessagePort 對象的創(chuàng)建依賴于 MessageChannel 類:
const channel = new MessageChannel();
const port1 = channel.port1
const port2 = channel.port2
// 或者簡寫為:
const { port1, port2 } = new MessageChannel();
實例化 MessageChannel 類之后,就產(chǎn)生了兩個 port :port1 和 port2 。這兩個 port 就是 MessagePort 對象。它就是我們上面那則故事提到的,可以用于兩個進程之間進行長期通信的關(guān)鍵所在。
舉個小例子,假設(shè)現(xiàn)在:
渲染進程一有了 port1
渲染進程二有了 port2
那么現(xiàn)在這兩個進程就可以通過 port.onmessage 和 port.postMessage 來收發(fā)彼此間的消息了:
// 渲染進程一:
port1.onmessage = (event) => {
console.log('received result:', event.data)
};
port1.postMessage('我是渲染進程一發(fā)送的消息');
// 渲染進程二:
port2.onmessage = (event) => {
console.log('received result:', event.data)
};
port2.postMessage('我是渲染進程二發(fā)送的消息');
只要 port1 和 port2 一直都存在,它們就可以進行持久通信,怎么樣,是不是很 niubility 。
OKK,那么現(xiàn)在如果是在渲染進程一創(chuàng)建的這兩個 port ,關(guān)鍵就是如何把 port2 給到另一個渲染進程二了。也就涉及到了 MessagePort 的傳遞。
1.2 ipcRenderer.postMessage()
說到 MessagePort 的傳遞就得談到 ipcRenderer 對象的 postMessage 方法了。因為 MessagePort 對象就是依靠它來傳遞。沒錯,此時應(yīng)該有小伙伴可能想起來了,我們平常網(wǎng)頁上的 window 對象也有一個 postMessage 方法,這兩者之間其實挺像的,只不過呢,是在不同的通道上。
portMessage 它的參數(shù)如下:
ipcRenderer.postMessage(channle, message, [transfer])
channel String:事件名
message any:要傳遞的消息
transfer MessagePort[] (optional):0個或多個 MessagePort 對象。
前兩個好理解,其實和 ipcRenderer 的其它方法差不多,事件,以及傳遞的消息。第三個參數(shù)有些特別,它是一個數(shù)組,其中可以傳遞 MessagePort 對象。這里需要注意,別看第三個參數(shù)標(biāo)記的是 optional ,但其實它也是需要傳遞的,如果你不需要傳 MessagePort 對象,那么就需要定義一個空數(shù)組,否則就會報錯啦。
另外,在之前的文章中,我們還有用到 ipcRenderer 的其它方法: send、 invoke 、 sendSync ,這三種方法主進程都是可以給渲染進程傳遞返回結(jié)果的,比如:
// render.js
const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染進程通過 invoke 發(fā)送的消息');
console.log('replyMessage', replyMessage); // "我是主進程返回的消息"
postMessage 的第二個參數(shù)也可以發(fā)送消息,那它是否也可以當(dāng)成 send 或者 invoke 來用呢?這里我測試了一下,發(fā)現(xiàn)主進程那邊不論是用 event.reply 還是用 event.returnValue 都不行,看來,官方還是希望我們遵循:”什么樣的API就做什么樣的事” ,而 ipcRenderer.postMessage ,他的主要職責(zé)就是用來發(fā)送 MessagePort 的。
并且!ipcRenderer.postMessage 只能通過 ipcMain.on 來接收到, ipcRenderer.on 是接收不到的!
這樣的話,看來如果我們要將某個 port 從一個渲染進程給到另一個渲染進程還是得依靠主進程了,需要它這個 媒人 來從中做媒。但問題不大,一旦這兩人連接上了,就不再需要媒人了。
同時我們發(fā)現(xiàn),通過這種方式我們也可以實現(xiàn)渲染進程與主進程之間的互相通信了,主進程在收到 port 的時候,如果不給其他人,自己用來和渲染進程通信也可以呀。
2. 主進程與渲染進程通信案例分析
好嘞,扯了這么多,讓我們先寫個小 demo,來看看通過 MessagePort 主進程與渲染進程是如何通信的吧。
和之前一樣,讓我們確定下要做什么事:
在某個時機,渲染進程創(chuàng)建了兩個 port 并將其中一個(名為 port1)發(fā)送給了主進程
渲染進程這邊的另一個 port2 綁定監(jiān)聽事件
主進程接收到 port1 并將它保存下來,同時也綁定監(jiān)聽事件
在另一個時機渲染進程通過 port2 給主進程發(fā)送消息
下面是 demo 的時序圖:
第一步、調(diào)整目錄結(jié)構(gòu)
由于這個案例說的是渲染進程與主進程的通信,讓我們基于之前的分支 example-3 再新建一個 example-4,同時刪除我們不用的窗口2,此時目錄結(jié)構(gòu)變?yōu)椋?br>
(example-4: https://github.com/LinDaiDai/electron-ipc-example/tree/example-4)
第二步、渲染進程提供生成 MessagePort 并發(fā)送給主進程的能力
之前提到了,做的第一件事:
在某個時機,渲染進程創(chuàng)建了兩個 port 并將其中一個(名為 port1)發(fā)送給了主進程
這里的某個時機,我們就在頁面上定義兩個按鈕吧:
點第一個按鈕創(chuàng)建并發(fā)送 port
點第二個按鈕給主進程發(fā)送消息
// window-one/index.html
<body>
<h1>Window One</h1>
<button onclick="sendPortToMain()">窗口1 postMessage 給主進程發(fā)送消息端口 port1</button>
<button onclick="sendMessageToMain()">窗口1 通過 port2 給主進程發(fā)送消息</button>
<script src="./renderer.js"></script>
</body>
對應(yīng)的渲染進程的代碼:
// window-one/render.js
const { ipcRenderer } = require('electron')
let portToMain
function sendPortToMain() {
// 1、創(chuàng)建一對 port
const { port1, port2 } = new MessageChannel()
// 2、給主進程傳輸消息端口 por1
ipcRenderer.postMessage(
'render-post-message-to-main',
'我是渲染進程一通過 ipcRenderer.postMessage 發(fā)送過來的',
[port1],
)
// 3、把 port2 賦值給 portToMain,方便其他模塊獲取
portToMain = port2
// 4、port2 綁定事件監(jiān)聽,之后主進程發(fā)送的消息都會在這里接收到
portToMain.onmessage = (event) => {
const data = event.data
console.log('[Renderer receive]message', data)
}
}
function sendMessageToMain() {
portToMain.postMessage('我是渲染進程一通過傳聲筒 port 發(fā)送過來的')
}
在上面代碼中,點擊第一個按鈕執(zhí)行 sendPortToMain 方法,其中創(chuàng)建了一堆 port ,并將其中一個通過 ipcRenderer.postMessage 發(fā)送給了主進程,同時設(shè)置監(jiān)聽。
點擊第二個按鈕,到時候會執(zhí)行 sendMessageToMain 方法,就可以利用 port 進行通信了。
第三步、主進程提供接收 port 和設(shè)置監(jiān)聽的能力
在第二步中,渲染進程發(fā)送了 port 給主進程,那么主進程這邊肯定要設(shè)置一個地方去接收,接收后同時也要保證它和渲染進程后續(xù)能持續(xù)通信。
那么只需要如下處理:
// main/ipc.js
const { ipcMain } = require('electron')
// 1、主進程監(jiān)聽一個事件,渲染進程想要發(fā)送 port 的話,就能在這里獲取到
ipcMain.on('render-post-message-to-main', (event, params) => {
console.log('[Main receive]render-post-message-to-main', params)
// 2、獲取到 port1
const port1 = event.ports[0]
// 3、需要調(diào)用一下 port1 的 start()
port1.start()
// 4、port1 綁定事件監(jiān)聽,之后渲染進程一發(fā)送的消息都會在這里接收到
port1.on('message', (event) => {
const data = event.data
console.log('[Main receive]message', data)
port1.postMessage('我是主進程通過 port 回復(fù)的消息')
})
})
(記得將 main/ipc.js 在主進程中引用一下哦)
// main/index.js
const ipc = require('./ipc')
// ...其他代碼
在上面的代碼中,我們先用 ipcMain 保證能接收到渲染進程發(fā)送過來的 port ,再調(diào)用 port1.start() ,然后給它綁定 message 事件,之后渲染進程一發(fā)送過來消息都能接收到,也能通過 port1 給渲染進程一發(fā)。
第四步、效果演示
一切代碼準(zhǔn)備就緒,讓我們啟動項目來看看效果。
分別點擊窗口中的第一個和第二個按鈕,能夠看到主進程和渲染進程的打印日志:
(終端里主進程的打印中文會亂碼,還請理解…)
3. 渲染進程與渲染進程案例預(yù)告
在看完了上面的案例之后,相信你對于這種用 postMessage 進行窗口間通信的方式有了一些了解。實際的開發(fā)場景中,我們可能還會進行渲染進程與渲染進程間的通信,甚至是同一個進程內(nèi)部之間的通信。在后面的文章中,我們會介紹如何通過 postMessage 來實現(xiàn)一個比較通用的 electron ipc 通信的庫,你也可以先利用上面的知識自己嘗試著看看可以如何去寫,敬請期待哦。
作者:LinDaiDai
歡迎關(guān)注微信公眾號 :前端晚間課
更多文章,收錄于小程序-互聯(lián)網(wǎng)小兵