zoukankan      html  css  js  c++  java
  • 研究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

  • 相关阅读:
    how to uninstall devkit
    asp.net中bin目录下的 dll.refresh文件
    查找2个分支的共同父节点
    Three ways to do WCF instance management
    WCF Concurrency (Single, Multiple, and Reentrant) and Throttling
    检查string是否为double
    How to hide TabPage from TabControl
    获取当前系统中的时区
    git svn cygwin_exception
    lodoop打印控制具体解释
  • 原文地址:https://www.cnblogs.com/lovesong/p/11180336.html
Copyright © 2011-2022 走看看