electron中的ipc通信(三)

4,093 阅读4分钟

深圳的天气骤降,还是挺冷的,大家要注意保暖。微信公众号:[猫十二的日常],欢迎留言和指出问题a

上次写了关于ipc通讯的一些方式,主要都是web页面向主进程通讯,主进程回复通讯,这个感觉和http有点相似,发送一个http资源的请求,服务返回一个资源结果,现在我们主要看这两个点

  • 主进程主动推消息给web渲染进程的
  • web渲染进程之间的通讯

主进程向渲染进程主动通信

这个可能就牵扯到了一个比较重要的APi,BrowserWindow 这个是用于创建和控制浏览器窗口的,只能在主进程进行调用,当然也是这个家伙创建的渲染进程

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('http://github.com')
const contents = win.webContents
console.log(contents)

这个创建之后返回的win下面的webContents就是可以渲染和控制web页面的api模块,咱们不会深入展开的,主要针对通讯讲一讲

contents.send(channel, ...args)

  • channel String 事件名称
  • ...args any[] 传递的参数,和上节一样

目录准备,修改和删除都在这几个文件操作

my-electron-app/
├── package.json  
├── main.js // 这个就是主进程
├── renderer.js // 这个就是渲染进程
└── index.html //web页面

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Electron演示</title>
</head>

<body>
    Electron演示
    <iframe src='./other.html'></iframe>
    <script src="./renderer.js"></script>
</body>

</html>

renderer.js

const { ipcRenderer } = require("electron");

ipcRenderer.on("message", (event, arg) => {
  console.log("主进程老哥,主动推消息了:", arg);
});

main.js

const { app, BrowserWindow, ipcMain } = require("electron");
function createWindow() {
  const win = new BrowserWindow({
    width: 1920,
    height: 1080,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  const contents = win.webContents;
  contents.openDevTools(); //打开调试工具
  win.loadFile("index.html");
  contents.on("did-finish-load", () => {
    //页面加载完成触发的回调函数
    console.log("load");
    contents.send("message", "我看到你加载完了,给你发个信息");
  });
}

app.whenReady().then(createWindow);

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

image-20210111143013911

contents.sendToFrame(frameId, channel, ...args) 通过frameId给渲染进程中嵌入的webview发送消息

  • frameId Integer | [number, number]
  • channel String
  • ...args any[]

首先如何拿到框架的id?

第一种渲染进程主动调用方法去拿到id,然后调用取方法

console.log('My frameId is:', require('electron').webFrame.routingId) //然后你把这个传给主进程

第二种主进程通过监听的函数去拿,因为渲染进程发消息会触发主进程的事件,我们就可以通过event去拿

ipcMain.on('ping', (event) => {
  console.info('Message came from frameId:', event.frameId)
})

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Electron演示</title>
</head>

<body>
    Electron演示
    <webview src='./other.html' webpreferences="nodeIntegration=true"></webview> //具体查api
    <script src="./renderer.js"></script>
</body>

</html>

main.js

const { app, BrowserWindow, ipcMain } = require("electron");
let win;
function createWindow() {
  win = new BrowserWindow({
    width: 1920,
    height: 1080,
    webPreferences: {
      nodeIntegration: true,
      webviewTag: true, //需要设置webview来启用这个
    },
  });
  const contents = win.webContents;
  contents.openDevTools(); //打开调试工具
  win.loadFile("index.html");
  contents.on("did-finish-load", () => {
    //页面加载完成触发的回调函数
    console.log("load");
    // contents.send("message", "我看到你加载完了,给你发个信息");
  });
}

ipcMain.on("getFrameId", (event, arg) => {
  console.log("收到了FrameId", arg); //收到了FrameId 1
  console.log("event收到了FrameId", event.frameId); //event收到了FrameId 1
  win.webContents.sendToFrame(
    event.frameId,
    "message",
    "我看到你加载完了,给你发个信息"
  );
});

app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

other.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>其他页面</title>
</head>

<body>
    <script>
        const {
            ipcRenderer,
            webFrame
        } = require("electron");
        ipcRenderer.send("getFrameId", webFrame.routingId);
        ipcRenderer.on("message", (event, arg) => {
            console.log("(renderer2)主进程老哥,主动推消息了:", arg);
        });
    </script>
</body>

</html>

renderer.js

const { ipcRenderer, webFrame } = require("electron");
console.log("My frameId is:", webFrame.routingId);

ipcRenderer.on("message", (event, arg) => {
  console.log("(renderer2)主进程老哥,主动推消息了:", arg);
});

结果

image-20210111151212189

image-20210111151228633

contents.postMessage(channel, message, [transfer]) 利用原生的webworker进行通讯

  • channel String
  • message any
  • transfer MessagePortMain[] (optional)

只写关键性代码,具体的代码参看最上面的改动,对照一下

main.js

contents.on("did-finish-load", () => {
    const { port1, port2 } = new MessageChannelMain();
    contents.postMessage("message", "我看到你加载完了,给你发个信息", [port1]);
    port2.postMessage("再给你发个消息");
});

renderer.js

ipcRenderer.on("message", (event, arg) => {
  const [port] = event.ports;
  console.log(arg, event);
  port.onmessage = (arg) => {
    console.log(arg.data);
  };
});

结果

  • 我看到你加载完了,给你发个信息 Object

  • 再给你发个消息

以上就是借助 webContent功能模块 主进程向渲染进程通信

渲染进程互相通信

ipcRenderer.sendTo(webContentsId, channel, ...args)

  • webContentsId Number 渲染进程的id
  • channel String
  • ...args any[]

获取渲染进程的id

  • 可以在创建窗口的时候保存每个窗口的id
  • 或者创建一个主进程事件获取每个渲染进程的id

main.js

const { app, BrowserWindow, ipcMain } = require("electron");
let win;
let ids = {
  "index.html": null,
};
function createWindow() {
  win = new BrowserWindow({
    width: 1920,
    height: 1080,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      webviewTag: true, //需要设置webview来启用这个
    },
  });
  const contents = win.webContents;
  contents.openDevTools(); //打开调试工具
  win.loadFile("index.html");
  ids["index.html"] = contents.id;
}

ipcMain.on("registerFrameId", (event, arg) => {
  console.log(arg);
  ids = Object.assign(ids, arg);
});

ipcMain.on("getFrameId", (event, arg) => {
  event.returnValue = ids[arg];
});

app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

renderer.js

const { ipcRenderer } = require("electron");

onload = () => {
  const webview = document.querySelector("webview");
  webview.addEventListener("did-finish-load", () => {
    webview.openDevTools();
    let otherId = ipcRenderer.sendSync("getFrameId", "other.html");
    console.log(otherId);
    ipcRenderer.sendTo(otherId, "webview", "other老弟,喝一口");
  });
  ipcRenderer.on("webview", (event, arg) => {
    console.log("来自other.html消息:", arg);
  });
};

other.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>其他页面</title>
</head>

<body>
    <script>
        onload = () => {
            const {
                ipcRenderer,
                remote //从remote模块获取到窗体的唯一id
            } = require("electron");
            // let webContent = webContents.getFocusedWebContents()
            const webContent = remote.getCurrentWebContents();
            console.log(webContent.id)
            ipcRenderer.send("registerFrameId", {
                'other.html': webContent.id
            });
            let indexId = ipcRenderer.sendSync('getFrameId', 'index.html')
            ipcRenderer.sendTo(indexId, "webview", "index老哥,给你发消息了");
            ipcRenderer.on("webview", (event, arg) => {
                document.write("来自html.html消息:", arg)
            });
        }
    </script>
</body>

</html>

Index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Electron演示</title>
</head>

<body>
    Electron演示
    <webview src='./other.html' webpreferences="nodeIntegration=true" id="webview"></webview>
    <script src="./renderer.js"></script>
</body>

</html>

结果

image-20210111165640002

ipcRenderer.sendToHost(channel, ...args)

  • channel String
  • ...args any[]

就像 ipcRenderer.send,不同的是消息会被发送到 host 页面上的 <webview> 元素,而不是主进程

需要用到webview的一个事件监听 'ipc-message' 监听发布的消息

具体改动这个地方

other.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>其他页面</title>
</head>

<body>
    asdada
    <script>
        const {
            ipcRenderer,
        } = require("electron");
        ipcRenderer.on('ping', (event, arg) => {
            console.log("来自主页面的回话", arg)
            ipcRenderer.sendToHost('message', '小哥我给你发信息了')
        })
    </script>
</body>
</html>

renderer.js

onload = () => {
  const webview = document.querySelector("webview");
  webview.addEventListener("ipc-message", (event) => {
    console.log("来自webview的回话:", event);
  });
  webview.addEventListener("did-finish-load", () => {
    webview.openDevTools();
    webview.send("ping", "理我一下");
  });
};

结果

image-20210111182953481

到这基本把ipc的基本的通信方法讲完了,剩下的就是各种炫技的方式了。看了下其他人的写法还是自己的路还是蛮长的,加油

如果觉得我的文章还可以,点个赞或者关注一下下,还有精彩的故事等着你哦,还可以云撸猫