我们知道开发Electron应用,难免要涉及到跨进程通信,以前Electron内置了remote模块,极大的简化了跨进程通信的开发工作,但这也带来了很多问题,具体的细节请参与我之前写的文章:

Electron团队把remote模块拿掉之后,开发者就只能使用ipcRenderer,ipcMain,webContents等模块收发跨进程消息了,这并没有什么问题,但写起来非常麻烦,跨进程消息多了之后,也很难管理维护。这就促使着我们思考如何实现一个大一统的跨进程事件组件。下面我就介绍一种方法。

首先这个组件整合了NodeJs的events模块和Electron收发事件的模块,所以先把这些模块引入进来

let events = require('events')
let { ipcRenderer, ipcMain, webContents } = require('electron')

我们假定这个组件的类名为Eventer,我们在这个类的构造函数中,实例化了一个EventEmitter对象,让它来负责监听和发射事件。

constructor() {
this.instance = new events.EventEmitter()
//this.instance.setMaxListeners(60) //Infinity
this.initEventPipe()
}

首先,无论是渲染进程还是主进程使用这个模块,都会执行这个构造函数,创建一个EventEmitter对象;但渲染进程的EventEmitter对象与主进程的EventEmitter对象是不同的;不同渲染进程间的EventEmitter对象也是不同的,但同一个进程内的EventEmitter对象是相同的,共享同一个EventEmitter对象,这里我们用到了单例模式,是通过下面这行代码实现的:

export let eventer = new Eventer()

也就是说某个进程第一次import这个组件的时候,Eventer类就实例化了,它的构造函数就执行过了,无论这个进程再import多少次这个类,都是引用的同一个eventer对象,这个类在同一个进程内不会被实例化多次。

默认情况下EventEmitter实例最多可为任何单个事件注册10个监听器,如果你嫌这个数量太少,可以通过setMaxListeners方法把这个数字设置大一些,设置为Infinity就没有任何数量限制了,但尽量不要这么做,要不然某个事件被反复注册了,你也不知道。

接下来我们就在initEventPipe方法内初始化了我们自己的跨进程消息管道

private initEventPipe() {
if (ipcRenderer) {
ipcRenderer.on('__eventPipe', (e: Electron.IpcRendererEvent, { eventName, eventArgs }) => {
this.instance.emit(eventName, e, eventArgs)
})
} else if (ipcMain) {
ipcMain.handle('__eventPipe', (e: Electron.IpcMainInvokeEvent, { eventName, eventArgs, broadcast }) => {
this.instance.emit(eventName, e, eventArgs)
if (!broadcast) return
webContents.getAllWebContents().forEach((wc) => {
if (wc.id != e.sender.id) {
wc.send('__eventPipe', { eventName, eventArgs })
}
})
})
}
}

在这个方法内,我们通过ipcRenderer、ipcMain是否存在来判断当前进程是渲染进程还是主进程;

如果是渲染进程则用ipcRenderer监听一个名为__eventPipe的消息;如果是主进程我们则通过ipcMain监听一个名为__eventPipe的消息。

无论是哪个进程,处理这个消息的回调函数都有两个参数,第一个参数是Electron为跨进程消息提供的消息体,第二个参数,是我们自己构造的(后面我们会讲),他们结构是相同的,都具有eventName和eventArgs属性;

在这个回调函数中,我们在当前进程的EventEmitter对象上发射一个事件,这个事件的名字就是eventName属性的值,事件有两个参数,一个是Electron为跨进程消息提供的消息体,另一个是eventArgs对应的值。

如果当前进程是主进程,我们还会进一步判断是不是有broadcast属性,如果有,那么就继续给所有其他的webContents发送__eventPipe消息,消息体是由eventName和eventArgs两个属性组成的。

这里我们通过e.sender.id来判断消息是从哪个渲染进程发来的,当转发这个消息给其他webContents时,要排除掉那个发来消息的webContents。

接下来我们看一下与事件发射有关的一系列方法

emitInProcess(eventName: string, eventArgs?: any) {
this.instance.emit(eventName, eventArgs)
}

这个方法在当前进程的EventEmitter对象上发射事件。它最简单了,不多做介绍。

emitCrossProcess(eventName: string, eventArgs?: any) {
if (ipcMain) {
webContents.getAllWebContents().forEach((wc) => {
wc.send('__eventPipe', { eventName, eventArgs })
})
} else if (ipcRenderer) {
ipcRenderer.invoke('__eventPipe', { eventName, eventArgs })
}
}

这个方法发射一个跨进程消息,如果是渲染进程调用这个方法,那么消息就是发送给主进程的,如果是主进程调用这个方法,那么消息就是发送给所有的渲染进程的。

消息的名字就是__eventPipe,消息体是eventName, eventArgs两个参数组成的对象,我们前面讲的initEventPipe方法内有监听这个消息的逻辑。

emitToAllProcess(eventName: string, eventArgs?: any) {
this.instance.emit(eventName, eventArgs)
if (ipcMain) {
webContents.getAllWebContents().forEach((wc) => {
wc.send('__eventPipe', { eventName, eventArgs })
})
} else if (ipcRenderer) {
ipcRenderer.invoke('__eventPipe', { eventName, eventArgs, broadcast: true })
}
}

这个方法可以把消息发送给所有进程,首先是在自己的进程上发射eventName事件,接着判断当前进程是主进程还是渲染进程,如果是主进程则给所有渲染进程发送消息,如果是渲染进程,则给主进程发送消息,给主进程发消息时,附加了broadcast标记。要求主进程给其他所有的渲染进程转发消息。

emitToWebContents(wcIdOrWc: number | WebContents, eventName: string, eventArgs?: any) {
if (ipcMain) {
if (typeof wcIdOrWc == 'number') {
webContents.getAllWebContents().forEach((wc) => {
if (wc.id === wcIdOrWc) wc.send('__eventPipe', { eventName, eventArgs })
})
} else {
wcIdOrWc.send('__eventPipe', { eventName, eventArgs })
}
} else if (ipcRenderer) {
ipcRenderer.sendTo(wcIdOrWc as number, '__eventPipe', { eventName, eventArgs })
}
}

这个方法把消息发送给指定的WebContents对象,如果当前进程是主进程,则找到WebContents对象,并调用它的send方法发送消息;如果当前进程是渲染进程,则使用ipcRenderer的sendTo方法发送给目标WebContents对象。

接下来还有几个注册事件和取消注册的方法

  on(eventName: string, callBack: (e: any, eventArgs: any) => void) {
this.instance.on(eventName, callBack)
}
once(eventName: string, callBack: (e: any, eventArgs: any) => void) {
this.instance.once(eventName, callBack)
}
off(eventName: string, callBack: (e: any, eventArgs: any) => void) {
if (callBack) {
this.instance.removeListener(eventName, callBack)
} else {
this.instance.removeAllListeners(eventName)
}
}

这些我们就不多做解释了。

遗留问题:我们没办法通过这个组件把消息透传到子页面iframe内部

这个组件淋漓尽致的体现了那句话:把简单、幸福留给用户;把复杂、无奈留给自己;

下面是我写的新书,这篇文章就提炼自这本书里的部分章节

自己实现一个Electron跨进程消息组件的更多相关文章

  1. Android随笔之——跨进程通信(一) Activity篇

    在Android应用开发中,我们会碰到跨进程通信的情况,例如:你用QQ通讯录打电话的时候会调用系统的拨号应用.某些新闻客户端可以将新闻分享到QQ.微信等应用,这些都是跨进程通信的情况.简而言之,就是一 ...

  2. [Hook] 跨进程 Binder 学习指南

    cp from : http://weishu.me/2016/01/12/binder-index-for-newer/ 毫不夸张地说,Binder是Android系统中最重要的特性之一:正如其名“ ...

  3. [Hook] 跨进程 Binder设计与实现 - 设计篇

    cp from : http://blog.csdn.net/universus/article/details/6211589 关键词 Binder Android IPC Linux 内核 驱动 ...

  4. 使用 SetParent 跨进程设置父子窗口时的一些问题(小心卡死)

    原文:使用 SetParent 跨进程设置父子窗口时的一些问题(小心卡死) 在微软的官方文档中,说 SetParent 可以在进程内设置,也可以跨进程设置.当使用跨进程设置窗口的父子关系时,你需要注意 ...

  5. Android 跨进程渲染

    本项目用于验证 Android 是否能够跨进程渲染 View,最终实现了在子进程创建WebView,主进程显示的功能. 一.跨进程渲染的意义 有一些组件比如 WebView 如果在主进程初始化,会大大 ...

  6. android不需要Socket的跨进程推送消息AIDL!

    上篇介绍了跨进程实时通讯http://www.cnblogs.com/xiaoxiaing/p/5818161.html 但是他有个缺点就是服务端无法推送消息给客户端,今天这篇文章主要说的就是服务器推 ...

  7. Android为TV端助力 不需要Socket的跨进程推送消息AIDL!

    上篇介绍了跨进程实时通讯http://www.cnblogs.com/xiaoxiaing/p/5818161.html 但是他有个缺点就是服务端无法推送消息给客户端,今天这篇文章主要说的就是服务器推 ...

  8. Android四大组件应用系列——使用ContentProvider实现跨进程通讯

    一.问题描述 如何在Android中实现不同应用之间的通讯(既跨进程进行调用)?Android提供了多种实现方式,使我们可以实现跨进程访问Activity.通过ContentProvider跨进程访问 ...

  9. Android四大组件应用系列5——使用AIDL实现跨进程调用Service

    一.问题描述 Android应用程序的四大组件中Activity.BroadcastReceiver.ContentProvider.Service都可以进行跨进程.在上一篇我们通过ContentPr ...

随机推荐

  1. 删除本地仓库中的lastUpdated文件.bat

    @echo off @ ECHO. @ ECHO. @ ECHO. 说 明 @ ECHO ------------------------------------------------------- ...

  2. 【程序员翻身计划】Java高性能编程第一章-Java多线程概述

    目标 重点: 线程安全的概念 线程通信的方式与应用 reactor线程模型 线程数量的优化 jdk常用命令 Netty框架的作用 难点 java运行的原理 同步关键字的原理 AQS的抽象 JUC的源码 ...

  3. js offset系列属性

    offsetParent:返回该元素有定位的父级,如果父级都没有定位则返回body offsetTop:返回元素相对父级(带有定位的父级)上方的偏移 offsetLeft:返回元素相对父级(带有定位的 ...

  4. 洛谷 P3214 - [HNOI2011]卡农(线性 dp)

    洛谷题面传送门 又是一道我不会的代码超短的题( 一开始想着用生成函数搞,结果怎么都搞不粗来/ll 首先不妨假设音阶之间存在顺序关系,最终答案除以 \(m!\) 即可. 本题个人认为一个比较亮的地方在于 ...

  5. CF1466G Song of the Sirens

    题目传送门 题意简述:给出 \(n,s_0,t\ (n=|t|)\),定义 \(s_i=s_{i-1}+t_i+s_{i-1}\).多次询问给出 \(k,m\),求 \(m\) 在 \(s_k\) 中 ...

  6. 【6】蛋白质组学鉴定定量软件之MaxQuant

    目录 1.简介 2.下载安装 3.配置与运行 4.结果 5.Perseus后处理 6.小结 1.简介 2016年,德国马普所的Cox和蛋白质组学领域巨擘Matthias Mann合作开发了MaxQua ...

  7. Oracle-with c as (select ......) 实现多次调用子查询结果

    with c as  (select a.trandt,sum(a.tranam) tranam from tran a group by a.trandt )   #将子查询抽取出来,以后可以直接重 ...

  8. (转载) Java多线程技术

    多线程编程一直是学员们比较头痛和心虚的地方,因为线程执行顺序的不可预知性和调试时候的困难,让不少人在面对多线程的情况下选择了逃避,采用单线程的方式,其实只要我们对线程有了明确的认识,再加上java内置 ...

  9. matplotlib以对象方式绘制子图

    matplotlib有两种绘图方式,一种是基于脚本的方式,另一种是面向对象的方式 面向脚本的方式类似于matlab,面向对象的方式使用起来更为简便 创建子图的方式也很简单 fig,ax = plt.s ...

  10. 巩固javaweb第十八天

    提交按钮 只要涉及提交信息,都应该提供一个提交按钮,当点击提交按钮的时候,用户输入的 信息将提交给服务器,意味着输入过程的结束.注册界面中也包含一个提交按钮. 提交按钮的基本格式如下: <inp ...