前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >electron 进程间通信

electron 进程间通信

作者头像
友儿
发布2024-03-27 08:22:34
710
发布2024-03-27 08:22:34
举报
文章被收录于专栏:友儿友儿

通过前面的学习,我们知道在Electron中,主进程和渲染进程是被换分成不同的功能的,主进程有完整的Node环境,而渲染进程默认是运行的是浏览器环境。这样划分的好处是:

  1. 安全性:渲染进程运行在沙盒环境中,限制了对底层操作系统的访问和敏感资源的操作。直接在渲染进程中开启 Node.js 环境可能会带来潜在的安全风险,因为 Node.js 具有强大的功能和系统级访问权限,可能会被恶意利用或不当使用。
  2. 性能:渲染进程主要负责显示用户界面,处理用户交互和渲染页面。将 Node.js 环境直接放在渲染进程中可能会对性能产生负面影响,因为 Node.js 的运行环境相对较重,可能会消耗大量的内存和 CPU 资源,从而影响渲染进程的响应性能和用户体验。
  3. 分离关注点:主进程和渲染进程在 Electron 中有不同的职责和关注点。主进程负责管理应用程序的生命周期、系统级功能和与底层操作系统的交互,而渲染进程负责处理用户界面和与用户的交互。通过进程间通信,可以保持这种分离,使代码更易于维护、调试和扩展。

但是我们知道,开发的时候,我们很可能需要在渲染进程中进行一些只能在主进程中运行的操作。这就需要进程间通信了,在渲染进程中需要的时候,可以给主进程发信息,让主进程帮忙处理。

我们之前说过一种直接给渲染进程开启node环境,使用remote模块的方式,并不十分推荐。

预加载脚本(preload

Electron 不推荐在渲染进程开启Nodejs 环境,那也就意味着我们无法在渲染进程中使用NodeJS API,但有时候我们又真的很需要使用NodeJS API。“Preload 脚本”就是用来解决这个问题的。

用大白话来说就是:我们可以在主进程创建窗口的时候,指定一些脚本(内容是我们定的),这些脚本将来是在渲染进程中使用的,但是先于网页内容加载,由于是在主进程的时候就预加载了,所以能使用NodeJS API。

从 Electron 20 开始,预加载脚本默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权,实际上,这意味着我们只拥有一个 require 函数,这个函数只能访问一组有限的 API。

可用的 API

详细信息

Electron 模块

仅限渲染进程模块

Node.js 模块

events、timers、url

Polyfilled 的全局模块

Buffer、process、clearImmediate、setImmediate

目前我使用的electron 的版本是 v25。下面写个示例体验一下:

创建一个预加载脚本 p1.js,我们可以在里面编写我们的代码,此脚本中可以使用Nodejs API。

代码语言:javascript
复制
// p1.js
const { contextBridge } = require('electron')
// 将xxxx对象暴露给渲染进程中的全局对象,以便在渲染进程中直接访问
contextBridge.exposeInMainWorld('xxxx', {
    name: '如花',
    run: function () {
        return '小子你跑的真快'
    }
})

contextBridge.exposeInMainWorld(apiKey, apiObject) 此方法将指定的 apiObject 对象暴露给渲染进程中的全局对象,以便在渲染进程中直接访问。apiKey 是一个字符串,用于在全局对象中创建一个属性,该属性将指向 apiObject

创建窗口的时候,指定preload配置

代码语言:javascript
复制
// main.js
const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    // 配置预加载脚本(这里需要是个绝对路径)
    preload: path.join(__dirname, 'p1.js')
  },
})
win.loadFile('index.html')

在渲染进程中使用预加载脚本暴露的方法

代码语言:javascript
复制
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>您好,世界</title>
</head>
<body>
    <h1>您好,世界!</h1>
    <script>
        // 调用预处理脚本中暴露的数据
        console.log(xxxx.name);
        console.log(xxxx.run());
    </script>
</body>
</html>

参考文档:https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-preload

进程间通信(IPC

在 Electron 中,使用 ipcMainipcRenderer 模块来处理进程间通信。

  • 在主进程中,可以使用 ipcMain 模块监听事件,通过 ipcMain.on() 方法注册事件处理程序,接收渲染进程发送的消息,并通过 event.sender.send() 方法向渲染进程发送回复。
  • 在渲染进程中,可以使用 ipcRenderer 模块发送消息,通过 ipcRenderer.send() 方法发送消息给主进程,并使用 ipcRenderer.on() 方法监听主进程发送的消息。

由于渲染进程中默认无法使用NodeJS API,也就无法使用 require 导入模块,所以我们需要将 ipcRenderer 模块的相关内容在预处理脚本中暴露,才能在渲染进程中使用。

渲染进程向主进程通信(单向)
代码语言:javascript
复制
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path');

app.on('ready', () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'p1.js')
        },
    });
    // 监听 fromSon 事件(频道)
    ipcMain.on('fromSon', function (event, arg1) {
        console.log(arg1);
        // 在主进程中设置窗口的标题
        win.setTitle(arg1)
    })
    win.loadFile('index.html')
})
代码语言:javascript
复制
// p1.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('elecAPI', {
    // 定义sendToFather方法,该方法可以在渲染进程中使用
    sendToFather: function (val) {
        // 使用ipcRenderer.send()方法向主进程指定频道发送信息
        ipcRenderer.send('fromSon', val)
    }
})
代码语言:javascript
复制
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>您好,世界</title>
</head>
<body>
    <h1>您好,世界!</h1>
    <button id="btn">发送</button>
    <script>
        let btn = document.getElementById('btn');
        btn.onclick = function () {
            // 调用预处理脚本中定义的方法,向主进程发送数据
            elecAPI.sendToFather('来自渲染进程的问候')
        }
    </script>
</body>
</html>

上面的代码中,我们在主进程中使用 ipcMain.on()方法监听 fromSon 频道(事件)。在渲染进程中使用 ipcRenderer.send() 方法向fromSon 频道发送数据。

渲染进程和主进程双向通信

这可以通过 ipcRenderer.invokeipcMain.handle 搭配使用来完成双向通信。

  • ipcRenderer.invoke() 方法允许渲染进程向主进程发送请求,并等待主进程返回结果。
  • ipcMain.handle() 方法可以为指定频道注册处理函数,这个处理函数可以接收请求的参数并执行相应的操作,然后返回一个结果给渲染进程。
代码语言:javascript
复制
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path');

app.on('ready', () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'p1.js')
        },
    });
    // 在主进程中监听 fromSon 事件,并为它绑定个处理函数。
    // 在处理函数中return的值就是返回给渲染进程的数据。
    ipcMain.handle('fromSon', function (event, arg1) {
        console.log(arg1);
        return '您的问候已收到,这是我的回复'
    })
    win.loadFile('index.html')
})
代码语言:javascript
复制
// p1.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('elecAPI', {
    // 定义sendToFather方法,该方法可以在渲染进程中使用
    sendToFather: function (val) {
        // 使用ipcRenderer.invoke()方法向主进程指定频道发送信息,它会返回一个Promise
        return ipcRenderer.invoke('fromSon', val)
    }
})
代码语言:javascript
复制
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>您好,世界</title>
</head>
<body>
    <h1>您好,世界!</h1>
    <button id="btn">发送</button>
    <script>
        let btn = document.getElementById('btn');
        btn.onclick = async function () {
            // 调用预处理脚本中定义的方法,向主进程发送数据,并接收返回值
            let res = await elecAPI.sendToFather('来自渲染进程的问候')
            console.log(res);
        }
    </script>
</body>
</html>
主进程向渲染进程通信(单向)

将消息从主进程发送到渲染进程时,需要指定是哪一个渲染进程接收消息。 消息需要通过该渲染进程的 WebContents 实例发送到渲染进程。 此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。

代码语言:javascript
复制
// main.js
const { app, BrowserWindow } = require('electron')
const path = require('path');

app.on('ready', () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'p1.js')
        },
    });
    win.loadFile('index.html')

    // 启动5秒后,向渲染进程发送数据
    setTimeout(function () {
        // 获取渲染进程的 WebContents,用使用它的send方法向渲染进程发送数据
        win.webContents.send('toSon', '来自主进程的问候')
    }, 5000)
})
代码语言:javascript
复制
// p1.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('elecAPI', {
    // 定义 fromFather方法,该方法可以在渲染进程中使用
    fromFather: function (callback) {
        // 使用ipcRenderer.on() 方法接收指定频道传来的数据,并用我们传入的处理函数处理它
        ipcRenderer.on('toSon', callback)
    }
})
代码语言:javascript
复制
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>您好,世界</title>
</head>
<body>
    <h1>您好,世界!</h1>
    <script>
        // 使用预处理脚本中定义的函数,间接监听从主进程中传开的数据
        elecAPI.fromFather(function (event, arg1) {
            console.log(arg1);
        })
    </script>
</body>
</html>
渲染进程之间的通信

目前没有直接的方法可以进行渲染进程之间的通信,不过可以将主进程作为渲染进程之间的消息代理。 这需要将消息从一个渲染进程发送到主进程,然后主进程将消息转发到另一个渲染进程。或者使用第三方存储方案(如:localStorage、数据库等)进行中转

参考文档:

https://www.electronjs.org/zh/docs/latest/tutorial/ipc

https://www.electronjs.org/zh/docs/latest/api/ipc-main

https://www.electronjs.org/zh/docs/latest/api/ipc-renderer

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 预加载脚本(preload)
  • 进程间通信(IPC)
    • 渲染进程向主进程通信(单向)
      • 渲染进程和主进程双向通信
        • 主进程向渲染进程通信(单向)
          • 渲染进程之间的通信
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档