IPC 通信设计文档
1. 整体流程
-
- 页面中调用 callipcBridge 注册一个方法, 注册事件(register-event)和传递参数给主进程
-
- 主进程通过(register-event)对参数进行处理, 注册事件(fire-event)和返回处理结果给渲染进程
-
- 渲染进程通过事件(fire-event)拿到处理结果, 做其他操作
2. 原来的IPC通信痛点
-
- 通信代码冗余, 需要注册很多没必要的事件名
-
- 管理混乱
-
- 渲染进程之间的通信复杂
3. 设计思路
-
- 所有的渲染进程在创建窗口时, 会挂载事件列表. 这样在任意的渲染进程都可以注册事件
-
- 提供一个fire-event中间层, 所有注册的事件, 都可以通过中间层传递数据
-
- 统一事件名,可以清晰看到事件的流向
-
- 统一管理
4. 主要的文件
-
- 注册事件, 处理注册事件的回调 public/ipcRegister/event.js
-
- 挂载在window上 public/ipcRegister/bridge.js
-
- preload中引入public/ipcRegister/bridge.js public/preload.js public/window/preload.js
-
- 主进程处理相关 src/ipcMain
-
- 主渲染进程处理相关 src/ipcRenderer/softwareUpdateIPC.js(示例)
5. 使用的步骤
以跳过此版本为例
- 注册事件
代码示例: 目录: public/ipc/event.js
/**
* @param {*} cb 主进程处理完成之后回调, 默认是null, 因为有些不需要回调
* @param {*} windowName 当前操作的窗口, 默认当前操作主窗口
* @param {*} params 其他的参数, 类型为keyValue的形式
*/
// 打开软件更新的弹框
openSoftwareUpdateWin: (cb = null, windowName = "mainWindow", params) => {
console.log("事件 - 打开软件更新的弹框")
registerEvent("openSoftwareUpdateWin", cb, { windowName, ...params })
},
- 调用事件, 传递参数
window.callipcBridge.openSoftwareUpdateWin(null, 'mainWindow', {
version,
summary,
data,
richData,
historyVersion,
current: packageConfig.appVersion,
// current: '1.16.0.5',
urlExt,
url,
items, // 图文数据
isUpdate, // 强制更新
autoUpdate: globalConfig.general.autoUpdate // 自动更新
})
- 主进程处理
代码示例:
目录: src/ipcMain/modules/softwareUpdate.ts
// 打开软件更新弹框
const openSoftwareUpdateWin = (app, args: IpcMainHandleParams): void => {
console.log(args, "注册时候传递过来的参数")
softwareUpdateWinInfo = args
if (!softwareUpdateWin) {
softwareUpdateWin = newBrowserWindow(
{
name: "softwareUpdate",
width: 406,
height: 412,
needMacSheet: false,
modal: process.platform == "darwin" ? false : true
},
"/windows/softwareUpdateWin/index.html"
)
// 监听关闭事件
softwareUpdateWin.on("closed", () => {
softwareUpdateWin = null
})
//监听子窗口数据采集并发给主窗口(行为分析)
monitorChildDataToMainWin(softwareUpdateWin)
}
}
- 主进程处理完成之后, 传递数据给其他渲染进程(如有需要)
// 主进程需要返回数据
以跳过此版本为例:
// 跳过此版本
const softwareUpdateWinSkipVersion = (
app,
args: IpcMainHandleParams
): KeyValue => {
if (softwareUpdateWin) {
softwareUpdateWin.close()
}
const mainWin = provideGlobalMap("mainWin")
if (!mainWin) {
console.error("主窗口被销毁")
return
}
const { version } = softwareUpdateWinInfo
return {
version,
...args
}
}
主渲染进程接收数据, 处理其他逻辑
代码示例: 目录: ipcRenderer/modules/softwareUpdateIPC
ipcRenderer.on('fire-event',(event, res) => {
console.log(res.payload, 'fire-res')
const eventName = res && res.payload && res.payload.eventName
if(eventName){
ipcRendererMap[eventName](res.payload)
}
})
const ipcRendererMap = {
'softwareUpdateWinSkipVersion': (payload) => {
store.dispatch('skipCurrentVersion', payload.version)
},
'softwareUpdateWinInstall': (payload) => {
store.dispatch('softwareInstall', payload)
}
}
- 注册事件的时候可以异步拿到回调(如有需要)
代码示例:
window.callipcBridge.getSoftwareUpdateWinInfo((err, res)=>{
if(!err){
console.log("获取软件更新信息的回调", res)
}
}, 'softwareUpdateWin')
6. 核心代码
- 事件注册 event.js
const { ipcRenderer } = require("electron")
const eventsMap = {} // 注册的事件列表
/**
* @param {*} eventName 注册的事件名
* @param {*} cb 主进程执行完后的回调
* @param {*} params 传递给主进程的参数
*/
function registerEvent(
eventName,
cb = null,
params = {}
) {
const timeStamp = String(new Date().getTime())
const ipcMainParams = Object.assign({ eventName, timeStamp }, params)
// 以timeStamp注册当前的callback
if (cb) {
eventsMap[timeStamp] = cb
}
console.log("传递给主进程的数据", ipcMainParams)
// 发送事件, ipcMainParams包含事件名, 时间戳和其他参数
ipcRenderer.send("register-event", ipcMainParams)
}
// 只针对window.callipcBridge.xxx, 注册的回调处理
ipcRenderer.on("fire-event",
(
event,
params
) => {
console.log("cb",eventsMap[params.timeStamp])
// 通过 timeStamp 找到当前的回调
const cb = eventsMap[params.timeStamp]
if (cb) {
if (params.err) {
cb(params.err, params.payload)
} else {
cb(false, params.payload)
}
// 执行完成之后,注销timeStamp回调,timeStamp 代表是当前eventName的一次事件请求, 请求完成之后,就清除
delete eventsMap[params.timeStamp]
}
}
)
module.exports = {
/**
* 在渲染进程调用
* @param {*} cb 操作完成之后的回调,默认是null, 因为有些不需要回调
* @param {*} window 当前操作的窗口, 默认当前操作主窗口
*/
// 打开软件更新的弹框
openSoftwareUpdateWin: (cb = null, windowName = "mainWindow", params) => {
console.log("事件 - 打开软件更新的弹框")
registerEvent("openSoftwareUpdateWin", cb, { windowName, ...params })
},
// 软件更新稍后提醒
softwareUpdateWinRemindMeLater: (
cb = null,
windowName = "mainWindow",
) => {
console.log("事件 - 稍后提醒")
registerEvent("softwareUpdateWinRemindMeLater", cb, {
windowName,
})
},
// 获取软件更新的信息
getSoftwareUpdateWinInfo(cb = null, windowName = "mainWindow") {
console.log("事件-获取软件更新信息", 'windowName', windowName)
registerEvent("getSoftwareUpdateWinInfo", cb, {
windowName,
})
},
// 跳过当前版本
softwareUpdateWinSkipVersion(cb = null, windowName = "mainWindow") {
console.log("事件- 跳过当前版本", 'windowName', windowName)
registerEvent("softwareUpdateWinSkipVersion", cb, {
windowName,
})
},
// 安装新版本
softwareUpdateWinInstall(cb = null, windowName = "mainWindow") {
console.log("事件- 安装新版本", 'windowName', windowName)
registerEvent("softwareUpdateWinInstall", cb, {
windowName,
})
},
}
2). 事件挂载 bridge.js
const eventsList = require('./event.js')
window.callipcBridge = {
...eventsList
}
3). 主进程处理 ipcMain.ts
const { ipcMain, app } = require('electron')
const softwareUpdate = require("./modules/softwareUpdate")
import { IpcMainEvent } from "electron"
import {
provideGlobalMap,
} from "../background/utils/utils"
function isPromise(obj: any):boolean {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
}
// 监听注册的事件
ipcMain.on('register-event', (event: IpcMainEvent, params: IpcMainHandleParams) => {
console.log('registered-event', '主进程接收到了的参数', params)
// 主进程对应的处理函数
const ipcMainHandleFn = eventsList[params.eventName]
console.log(ipcMainHandleFn, '主进程对应的处理函数')
const mainWindow = provideGlobalMap('mainWin')
if (ipcMainHandleFn) {
const result = ipcMainHandleFn(app, params)
console.log('主进程处理完之后的结果result',result ,isPromise(result))
// 向其他的渲染进程发送处理完成之后的结果
if (isPromise(result)) {
console.log("主线程发送数据给渲染进程 -- promise数据")
result.then(res => {
event.sender.send('fire-event', {
timeStamp: params.timeStamp,
payload: res
})
mainWindow.webContents.send('fire-event', {
timeStamp: params.timeStamp,
payload: res
})
}).catch(err => {
event.sender.send('fire-event', {
timeStamp: params.timeStamp,
err
})
mainWindow.webContents.send('fire-event', {
timeStamp: params.timeStamp,
err
})
})
} else {
console.log("主线程发送数据给渲染进程 - 其他数据")
event.sender.send('fire-event', {
timeStamp: params.timeStamp,
payload: result
})
mainWindow.webContents.send('fire-event', {
timeStamp: params.timeStamp,
payload: result
})
}
} else {
console.log("找不到主进程对应的处理函数");
event.sender.send('fire-event', {
timeStamp: params.timeStamp,
err: new Error('event not support')
})
mainWindow.webContents.send('fire-event', {
timeStamp: params.timeStamp,
err: new Error('event not support')
})
}
})
/**
* 维护主进程要处理的事件列表
*/
const eventsList = {
...softwareUpdate // 软件更新模块的事件列表
}
- 渲染进程处理 ipcRenderer/softwareUpdateIPC.js
import { ipcRenderer } from 'electron'
import store from '@/store'
ipcRenderer.on('fire-event',(event, res) => {
console.log(res.payload, 'fire-res')
const eventName = res && res.payload && res.payload.eventName
if(eventName){
ipcRendererMap[eventName](res.payload)
}
})
const ipcRendererMap = {
// 跳过当前版本
'softwareUpdateWinSkipVersion': (payload) => {
store.dispatch('skipCurrentVersion', payload.version)
},
// 安装新版本
'softwareUpdateWinInstall': (payload) => {
store.dispatch('softwareInstall', payload)
}
}