• 研究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({  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

  • 相关阅读:
    [记录] 原生JS 的常用方法封装
    [记录] JavaScript 中的DOM操作
    [记录] JavaScript 中的正则表达式(案例分析)
    图片查看器插件(带缩略图) viewer.js
    (二) 美化滚动条 JScrollPane.js
    关于数据库存储过程管理的一点建议
    根据属性名称来改变XML节点值
    hdu 3911 Black And White
    poj 3667 Hotel
    hdu 4217 Data Structure?
  • 原文地址:https://www.cnblogs.com/lovesong/p/11180336.html
Copyright © 2020-2023  润新知