Electron入门教程3 ——进程通信

简介: Electron入门教程3 ——进程通信

✧ 渲染进程向主进程的单向通信


在Electron中,进程通过开发人员定义的“通道”与ipcMain模块和ipcRenderer模块进行通信。这些通道是任意的(您可以任意命名它们)和双向的(您可以为两个模块使用相同的通道名称)。要从渲染进程向主进程发送单向IPC消息,可以再预渲染脚本preload.js里使用ipcRenderer发送API发送消息,然后在main.js里用ipcMain.on接收。你通常使用这个模式从你的web内容中调用一个主进程API。我们将通过创建一个简单的应用程序来演示这种模式,该应用程序可以通过编程方式更改窗口的标题。


下面我们用代码来演示一下这个过程,下面是案例的所有代码:


index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>进程通信</title>
</head>
<body>
Title: <input id="title"/>
<button id="btn" type="button">Set</button>
<script src="./index.js"></script>
</body>
</html>


preload.js


const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})


index.js


const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});


main.js


const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
function createWindow () {
    const mainWindow = new BrowserWindow({
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })
    ipcMain.on('set-title', (event, title) => {
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })
    mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
    createWindow()
    app.on('activate', function () {
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})
app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') app.quit()
})


运行效果如下(GIF有点慢,别介意):


42602c25ba1f408685ab0ddad3f6b195.gif


下面对代码的一些要点进行讲解:


1.在主进程中监听事件


在主进程中,我们使用ipcMain在set-title通道上设置一个IPC监听器,这个set-title是我们在预渲染脚本preload.js里面定义的接口通道。


    ipcMain.on('set-title', (event, title) => {
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })

每当消息通过set-title通道传入时,此函数将找到附加到消息发送者的BrowserWindow实例,并使用win.setTitle设置应用窗口的标题。


2.在预加载脚本里面通过定义接口通道


要向上面创建的侦听器发送消息,您可以使用ipcRenderer。发送API。默认情况下,渲染器进程没有Node.js或Electron模块访问。作为应用程序开发人员,您需要使用contextBridge 从预加载脚本中选择要公开哪些API。此时,您将能够在呈现过程中使用window.electronAPI.setTitle()函数。


const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})


✧ 渲染进程与主进程的双向通信


双向IPC的一个常见应用是从渲染进程代码中调用主进程模块并等待结果。这可以通过使用ipcRenderer.invoke来实现,调用ipcMain.handle配对。在下面的例子中,我们将从渲染进程中打开一个选择本地文件对话框,并返回所选文件的路径。


下面是案例涉及的所有代码:


index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>进程通信</title>
</head>
<body>
<button type="button" id="btn">Open a File</button>
File path: <strong id="filePath"></strong>
<script src='./index.js'></script>
</body>
</html>


index.js


const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})


main.js


const {app, BrowserWindow, ipcMain,dialog} = require('electron')
const path = require('path')
async function handleFileOpen() {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (canceled) {
    return ""
  } else {
    return filePaths[0]
  }
}
function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
  ipcMain.handle('openFileDialog', handleFileOpen)
  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})


preload.js


const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI',{
  openFile: () => ipcRenderer.invoke('openFileDialog')
})


image.gif

下面对代码的一些要点进行讲解:

1.在主进程定义事件处理函数,并监听ICP接口的调用

在主进程中,我们将创建一个调用dialog模块的showOpenDialog方法的函数handleFileOpen(),用于返回用户选择的文件路径的值。在应用准备好之后,里面调用ipcMain.handle()来监听渲染进程里的ipcRenderer.invoke('openFileDialog')里定义的openFileDialog。当index.js里面调用window.electronAPI.openFile()时,会触发openFileDialog,进而被主进程监听处理后,返回结果。


2. 调用通过预加载脚本定义接口

在预加载脚本中,我们公开了一个单行openFile函数,它调用并返回ipcRederer .invoke('openFileDialog')。

在index.js代码片段中,我们监听对#btn按钮的点击,并调用window.electronAPI.openFile() 来激活本地的openFile对话框。然后在#filePath元素中显示选定的文件路径。


3. ipcRenderer.invoke的替代

ipcRenderer.invoke()有两种替代方式:

(1)ipcRenderer.send() :我们所使用的单向通信也可以用来执行双向通信。这是在Electron 7之前通过IPC进行异步双向通信的推荐方式。


preload.js


const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (_event, arg) => {
  console.log(arg)
  // 会打印pong
})
ipcRenderer.send('asynchronous-message', 'ping')


main,js


ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg)
  //会答应ping
  event.reply('asynchronous-reply', 'pong')
})


(1) ipcRenderer.sendSync() : 这个方法向主进程发送消息,并同步等待响应。

preload.js


const { ipcRenderer } = require('electron')
const result = ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(result)
// 会打印pong


main.js


const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg)
  // 会打印ping
  event.returnValue = 'pong'
})


此代码的结构与调用模型非常相似,但出于性能原因,我们建议避免使用此API。它的同步特性意味着它将阻塞呈现程序进程,直到接收到应答。


✧ 主进程向渲染进程的单向通信


当从主进程向渲染进程发送消息时,您需要指定哪个渲染程序正在接收消息。消息需要通过主进程的WebContents实例发送到渲染进程。这个WebContents实例包含一个sent方法,可以像ipcReender .send那样使用它。为了演示这个通信模式,将构建一个由菜单栏控制的数字计数器。


index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>进程通信</title>
</head>
<body>
Current value: <strong id="counter">0</strong>
<script src='./index.js'></script>
</body>
</html>


preload.js


const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
  handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})


index.js


const counter = document.getElementById('counter')
window.electronAPI.handleCounter((event, value) => {
    const oldValue = Number(counter.innerText)
    const newValue = oldValue + value
    counter.innerText = newValue
    event.sender.send('counter-value', newValue)
})


main.js


const {app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')
function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
      {
        click: () => mainWindow.webContents.send('update-counter', 1),
        label: 'Increment',
      },
      {
        click: () => mainWindow.webContents.send('update-counter', -1),
        label: 'Decrement',
      }
      ]
    }
  ])
  Menu.setApplicationMenu(menu)
  mainWindow.loadFile('index.html')
  // Open the DevTools.
  mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})


运行效果演示:


33116284d0984b21a672d748585b9153.gif


对部分代码讲解:

我们首先需要在主流程中使用Electron的Menu模块构建一个自定义菜单,从主进程向目标渲染器发送IPC消息。单击处理程序通过计数器通道向呈现程序进程发送消息(1或-1)。


const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
        {
          click: () => mainWindow.webContents.send('update-counter', 1),
          label: 'Increment',
        },
        {
          click: () => mainWindow.webContents.send('update-counter', -1),
          label: 'Decrement',
        }
      ]
    }
  ])
  Menu.setApplicationMenu(menu)


就像前面渲染到主进程的例子一样,我们在预加载脚本preload.js中使用contextBridge和ipcRederer模块向渲染进程公开IPC功能:


const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
    handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})


✧ 渲染进程之间的通信


在Electron中,没有直接的方法在渲染进程之间使用ipcMain和ipRenderer模块发送消息,而且这种通信方式其实也非常少用。要做到这一点,你可以使用主进程作为渲染程序之间的消息代理。这将涉及到从一个渲染器向主进程发送消息,主进程将把消息转发给另一个渲染器,这里就不做演示了。


相关文章
|
2月前
|
安全
【进程通信】信号的捕捉原理&&用户态与内核态的区别
【进程通信】信号的捕捉原理&&用户态与内核态的区别
|
2月前
|
Shell
【进程通信】利用管道创建进程池(结合代码)
【进程通信】利用管道创建进程池(结合代码)
|
27天前
|
存储 安全 Python
进程通信 , 信号量 , 队列 , 管道 , 共享内存
进程通信 , 信号量 , 队列 , 管道 , 共享内存
|
2月前
|
消息中间件 Linux C语言
进程通信:管道与队列
进程通信:管道与队列
|
2月前
|
存储 安全 调度
【操作系统】进程控制与进程通信
【操作系统】进程控制与进程通信
29 3
|
2月前
|
NoSQL Linux Shell
【进程通信】了解信号以及信号的产生
【进程通信】了解信号以及信号的产生
|
2月前
【进程通信】Syetem V 共享内存(结合代码模拟通信)
【进程通信】Syetem V 共享内存(结合代码模拟通信)
|
2月前
【进程通信】用命名管道模拟server和client之间的通信
【进程通信】用命名管道模拟server和client之间的通信
|
2月前
|
消息中间件 安全 Unix
【进程通信】进程通信--匿名管道
【进程通信】进程通信--匿名管道

热门文章

最新文章