electron IPC通信管理

222 阅读5分钟

IPC 通信设计文档

1. 整体流程

    1. 页面中调用 callipcBridge 注册一个方法, 注册事件(register-event)和传递参数给主进程
    1. 主进程通过(register-event)对参数进行处理, 注册事件(fire-event)和返回处理结果给渲染进程
    1. 渲染进程通过事件(fire-event)拿到处理结果, 做其他操作

2. 原来的IPC通信痛点

    1. 通信代码冗余, 需要注册很多没必要的事件名
    1. 管理混乱
    1. 渲染进程之间的通信复杂

3. 设计思路

    1. 所有的渲染进程在创建窗口时, 会挂载事件列表. 这样在任意的渲染进程都可以注册事件
    1. 提供一个fire-event中间层, 所有注册的事件, 都可以通过中间层传递数据
    1. 统一事件名,可以清晰看到事件的流向
    1. 统一管理

4. 主要的文件

    1. 注册事件, 处理注册事件的回调 public/ipcRegister/event.js
    1. 挂载在window上 public/ipcRegister/bridge.js
    1. preload中引入public/ipcRegister/bridge.js public/preload.js public/window/preload.js
    1. 主进程处理相关 src/ipcMain
    1. 主渲染进程处理相关 src/ipcRenderer/softwareUpdateIPC.js(示例)

5. 使用的步骤

以跳过此版本为例

  1. 注册事件

代码示例: 目录: 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 })
  },
  1. 调用事件, 传递参数
   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 // 自动更新
   })
  1. 主进程处理

代码示例:

目录: 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)
  }
}
  1. 主进程处理完成之后, 传递数据给其他渲染进程(如有需要)

// 主进程需要返回数据

以跳过此版本为例:

  // 跳过此版本
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)
    }
}
  1. 注册事件的时候可以异步拿到回调(如有需要)

代码示例:

    window.callipcBridge.getSoftwareUpdateWinInfo((err, res)=>{
        if(!err){
          console.log("获取软件更新信息的回调", res)
        }
    }, 'softwareUpdateWin')

6. 核心代码

  1. 事件注册 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  // 软件更新模块的事件列表
}
  1. 渲染进程处理 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)
    }
}