Electron + Vue3开源跨平台壁纸工具实战(七)进程通信

系列

Electron + Vue3开源跨平台壁纸工具实战(一)

Electron + Vue3开源跨平台壁纸工具实战(二)本地运行

Electron + Vue3开源跨平台壁纸工具实战(三)主进程

Electron + Vue3开源跨平台壁纸工具实战(四)主进程-数据管理(1)

Electron + Vue3开源跨平台壁纸工具实战(五)主进程-数据管理(2)

Electron + Vue3开源跨平台壁纸工具实战(六)子进程服务

Electron + Vue3开源跨平台壁纸工具实战(七)进程通信

Electron + Vue3开源跨平台壁纸工具实战(八)主进程-核心功能

源码

省流点我进Github获取源码,欢迎fork、star、PR

进程通信


概述

飞鸟壁纸采用多进程架构,包含主进程、渲染进程和子进程。不同进程之间通过不同的通信机制进行数据交换和功能协作。本文档详细介绍主进程与渲染进程、主进程与子进程之间的通信实现。


主进程与渲染进程通信

1. 通信架构

主进程与渲染进程之间的通信通过预加载脚本(Preload Script)实现,采用 contextBridgeipcRenderer 技术。

功能说明: 预加载脚本是 Electron 安全架构的核心组件,它在渲染进程加载之前执行,可以访问 Node.js API 和 Electron API。通过 contextBridge,我们可以安全地将主进程的功能暴露给渲染进程,同时保持上下文隔离的安全性。这种方式避免了直接暴露 Node.js API 给渲染进程,提高了应用的安全性。

实现原理:

  1. 预加载脚本在渲染进程的隔离上下文中运行
  2. 通过 contextBridge.exposeInMainWorld() 将 API 暴露给渲染进程
  3. 渲染进程通过 window.FBW 对象访问这些 API
  4. 所有通信都是异步的,不会阻塞 UI 线程
js:src/preload/index.mjs 复制代码
import { contextBridge, ipcRenderer } from 'electron'

// 创建一个对象来存储已注册的回调引用
const listeners = {
  jumpToPage: null,
  commonData: null,
  sendPostData: null,
  settingDataUpdate: null,
  sendMsg: null,
  triggerAction: null,
  setVideoPath: null,
  setVideoSource: null,
  setVideoMute: null,
  setVideoFrameRate: null,
  setVideoScaleMode: null,
  setVideoBrightness: null,
  setVideoContrast: null
}

const api = {
  onSettingDataUpdate: (callback) => {
    listeners.settingDataUpdate = callback
    ipcRenderer.on('main:settingDataUpdate', callback)
  },
  offSettingDataUpdate: () => {
    if (listeners.settingDataUpdate) {
      ipcRenderer.off('main:settingDataUpdate', listeners.settingDataUpdate)
      listeners.settingDataUpdate = null
    }
  },

  // 方法调用
  search: (...args) => ipcRenderer.invoke('main:search', ...args),
  getSettingData: (...args) => ipcRenderer.invoke('main:getSettingData', ...args)
}

// 暴露给渲染进程
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('FBW', api)
  } catch (err) {
    console.error(err)
  }
} else {
  window.FBW = api
}

2. 通信方式

2.1 渲染进程调用主进程方法

功能说明: 渲染进程通过预加载脚本暴露的 API 调用主进程的方法。这种方式实现了渲染进程与主进程之间的双向通信,渲染进程可以请求主进程执行各种操作,如文件操作、数据库查询、系统调用等。所有调用都是异步的,返回 Promise 对象,不会阻塞渲染进程的 UI 线程。

使用场景:

  • 搜索图片和视频资源
  • 获取应用设置数据
  • 执行文件操作(选择文件、删除文件等)
  • 控制窗口行为(打开、关闭、调整大小等)
  • 设置壁纸和动态壁纸
js:src/renderer/windows/MainWindow/components/ExploreCommon.vue 复制代码
// 渲染进程中调用主进程方法
const res = await window.FBW.search({
  resourceType,
  resourceName,
  startPage,
  pageSize,
  isRandom,
  sortField,
  sortType,
  filterKeywords,
  filterType,
  quality: quality.toString(),
  orientation: orientation.toString()
})
2.2 主进程向渲染进程发送事件

功能说明: 主进程可以向渲染进程发送事件,实现主进程到渲染进程的单向通信。这种方式通常用于通知渲染进程某些状态发生了变化,如设置更新、数据同步、系统事件等。主进程可以同时向多个窗口发送事件,确保所有窗口都能接收到最新的状态信息。

使用场景:

  • 设置数据更新通知
  • 系统状态变化通知
  • 错误和警告消息
  • 进度更新通知
  • 实时数据同步
js:src/main/store/index.mjs 复制代码
// 主进程中向渲染进程发送事件
sendSettingDataUpdate() {
  if (global.FBW.mainWindow.win) {
    global.FBW.mainWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  }
  if (global.FBW.viewImageWindow.win) {
    global.FBW.viewImageWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  }
  if (global.FBW.suspensionBall.win) {
    global.FBW.suspensionBall.win.webContents.send('main:settingDataUpdate', this.settingData)
  }
  if (global.FBW.dynamicWallpaperWindow.win) {
    global.FBW.dynamicWallpaperWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  }
  if (global.FBW.rhythmWallpaperWindow.win) {
    global.FBW.rhythmWallpaperWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  }
}

代码说明:

  • 使用 webContents.send() 向特定窗口发送事件
  • 分别检查每个窗口是否存在,避免向未创建的窗口发送消息
  • 这种方式比 BrowserWindow.getAllWindows().forEach() 更精确,可以针对不同窗口发送不同的数据
  • 确保设置数据在所有窗口间保持同步
2.3 渲染进程监听主进程事件

功能说明: 渲染进程可以监听主进程发送的事件,实现事件驱动的响应机制。通过注册事件监听器,渲染进程可以实时响应主进程的状态变化和通知。这种机制确保了渲染进程能够及时更新界面状态,提供良好的用户体验。

生命周期管理:

  • 在组件挂载前注册事件监听器
  • 在组件卸载前解绑事件监听器,防止内存泄漏
  • 使用统一的回调函数处理不同类型的事件

事件类型:

  • 设置数据更新事件
  • 通用数据同步事件
  • 消息通知事件
  • 系统状态变化事件
js:src/renderer/components/CommonApp.vue 复制代码
// 渲染进程中监听主进程事件
onBeforeMount(async () => {
  // 监听设置数据更新事件
  window.FBW.onSettingDataUpdate(onSettingDataUpdateCallback)
  // 监听主进程通用数据
  window.FBW.onCommonData(onCommonDataCallback)
  // 监听主进程消息
  window.FBW.onSendMsg(onSendMsgCallback)

  await init()
})

onBeforeUnmount(() => {
  // 取消监听设置数据更新事件
  window.FBW.offSettingDataUpdate(onSettingDataUpdateCallback)
  // 取消监听主进程通用数据
  window.FBW.offCommonData(onCommonDataCallback)
  // 取消监听主进程消息
  window.FBW.offSendMsg(onSendMsgCallback)
})

3. 安全机制

功能说明: Electron 应用的安全机制通过窗口配置选项实现,确保应用在提供丰富功能的同时保持安全性。通过启用上下文隔离、禁用 Node.js 集成、使用预加载脚本等方式,可以有效防止恶意代码的执行和资源访问。

安全特性:

  • 上下文隔离:渲染进程与主进程完全隔离,无法直接访问 Node.js API
  • 预加载脚本:通过受控的 API 暴露功能,而不是直接暴露所有 API
  • 沙箱模式:在需要文件系统访问时禁用沙箱,但通过其他方式保证安全
  • CSP 策略:通过内容安全策略限制资源加载
js:src/main/windows/MainWindow.mjs 复制代码
// 主进程窗口配置
const options = {
  width: 1020,
  height: 700,
  minWidth: 1020,
  minHeight: 700,
  show: false,
  frame: false,
  backgroundColor: '#efefef',
  autoHideMenuBar: true,
  titleBarStyle: 'hidden',
  icon: global.FBW.iconLogo,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.mjs'),
    sandbox: false,
    webSecurity: false,
    contextIsolation: true,
    nodeIntegration: false,
    allowRunningInsecureContent: true,
    devTools: true
  }
}

主进程与子进程通信

1. 子进程类型

飞鸟壁纸中的子进程主要包括:

  • H5 服务子进程:提供本地 HTTP/HTTPS 服务,支持 Socket.IO 实时通信
  • 文件服务子进程:处理文件扫描、图片质量计算等操作

2. 通信架构

2.1 子进程创建

飞鸟壁纸使用 Electron 的 utilityProcess 来创建子进程,这是 Electron 推荐的安全方式。

功能说明: 子进程创建是主进程与子进程通信的基础。通过 utilityProcess 创建的子进程具有更好的安全性和性能,比传统的 child_process 更适合 Electron 应用。子进程可以独立运行,即使崩溃也不会影响主进程的稳定性。

技术优势:

  • 安全性utilityProcess 提供了更好的安全隔离
  • 性能 :比传统的 child_process 更高效
  • 稳定性:子进程崩溃不会影响主进程
  • 资源管理:更好的内存和 CPU 资源管理

通信机制:

  • 使用 MessageChannelMain 创建消息通道
  • 通过端口进行双向通信
  • 支持传递可转移对象(如端口本身)
js:src/main/child_server/ChildServer.mjs 复制代码
import { utilityProcess, MessageChannelMain } from 'electron'

export default class ChildServer {
  #serverName
  #serverPath
  #child
  #port2

  constructor(serverName, serverPath) {
    global.logger.info(
      `ChildServer INIT:: serverName => ${serverName}, serverPath => ${serverPath}`
    )
    this.#serverName = serverName
    this.#serverPath = serverPath
    this.#child = null
    this.#port2 = null
  }

  start({ options, onMessage = () => {} } = {}) {
    const { port1, port2 } = new MessageChannelMain()
    this.#child = utilityProcess.fork(this.#serverPath, options)
    global.logger.info(`ChildServer START:: serverName => ${this.#serverName}`)
    this.#port2 = port2

    this.#child.on('exit', () => {
      global.logger.info(`ChildServer EXIT:: serverName => ${this.#serverName}`)
      this.#child = null
      this.#port2 = null
    })

    this.#port2.on('message', onMessage)

    this.#port2.start()
    // 初始消息
    this.#child.postMessage(
      {
        serverName: this.#serverName,
        event: 'SERVER_FORKED'
      },
      [port1]
    )
    // 服务启动消息
    this.postMessage({
      serverName: this.#serverName,
      event: 'SERVER_START'
    })
  }

  stop(callback) {
    global.logger.info(`ChildServer STOP:: serverName => ${this.#serverName}`)
    if (!this.#child) {
      global.logger.info(
        `ChildServer STOP FAILED:: SERVER NOT START, serverName => ${this.#serverName}`
      )
      return
    }
    const isSuccess = this.#child?.kill()
    typeof callback === 'function' && callback(isSuccess)
  }

  postMessage(data) {
    this.#port2?.postMessage(data)
  }
}
2.2 主进程向子进程发送消息

功能说明: 主进程可以向子进程发送消息,控制子进程的行为和获取子进程的执行结果。通过消息传递机制,主进程可以启动子进程服务、发送配置参数、请求数据处理等。这种通信方式支持异步操作,不会阻塞主进程的执行。

消息类型:

  • 服务启动消息:启动 H5 服务、文件服务等
  • 配置更新消息:发送设置变更、参数调整等
  • 任务请求消息:请求文件扫描、图片处理等
  • 控制消息:停止服务、重启服务等

错误处理:

  • 支持重试机制,提高服务启动的可靠性
  • 完善的错误日志记录
  • 优雅的失败处理
js:src/main/store/index.mjs 复制代码
// 主进程中启动 H5 服务
handleH5ServerStart(maxRetries = 3, retryInterval = 2000) {
  let retryCount = 0

  const attemptStart = () => {
    try {
      this.h5Server?.start({
        options: {
          env: process.env
        },
        onMessage: async ({ data }) => {
          switch (data.event) {
            case 'SERVER_START::SUCCESS': {
              this.h5ServerUrl = data.url
              global.logger.info(`H5服务器启动成功: ${this.h5ServerUrl}`)

              // 发送消息到主窗口
              if (global.FBW.mainWindow.win) {
                global.FBW.sendCommonData(global.FBW.mainWindow.win)
                global.FBW.sendMsg(global.FBW.mainWindow.win, {
                  type: 'success',
                  message: t('messages.h5ServerStartSuccess')
                })
              }
              break
            }
            case 'SERVER_START::FAIL': {
              global.logger.error(`H5服务器启动失败: ${data}`)
              break
            }
            case 'SERVER_LOG': {
              const type = data.level
              if (type && typeof global.logger[type] === 'function') {
                global.logger[type](data.message)
              } else {
                global.logger.info(`[H5Server] INFO => ${data.message}`)
              }
              break
            }
          }
        }
      })
    } catch (err) {
      global.logger.error(`启动H5服务器失败: ${err}`)
      // 重试逻辑...
    }
  }

  // 开始第一次尝试
  attemptStart()
}

// 向 H5 子进程发送设置更新
async updateSettingData(data) {
  // 更新设置
  const res = await this.settingManager.updateSettingData(data)
  if (res.success) {
    // 向H5子进程发送设置更新
    this.h5Server?.postMessage({
      event: 'APP_SETTING_UPDATED',
      data: this.settingData
    })
    // 发送更新消息
    this.sendSettingDataUpdate()
  }
  return res
}
2.3 子进程接收主进程消息

功能说明: 子进程通过消息通道接收来自主进程的消息,并根据消息类型执行相应的操作。子进程可以访问主进程的模块和资源,实现复杂的数据处理和服务提供功能。通过事件驱动的方式处理消息,确保子进程能够及时响应主进程的请求。

服务类型:

  • H5 服务:提供本地 HTTP/HTTPS 服务,支持 Socket.IO 实时通信
  • 文件服务:处理文件扫描、图片质量计算等耗时操作
  • 数据处理服务:执行复杂的数据分析和处理任务

消息处理:

  • 使用事件驱动架构处理不同类型的消息
  • 支持异步操作,不会阻塞消息接收
  • 完善的错误处理和日志记录
js:src/main/child_server/h5_server/index.mjs 复制代码
/**
 * h5服务子进程
 * */
import DatabaseManager from '../../store/DatabaseManager.mjs'
import SettingManager from '../../store/SettingManager.mjs'
import ResourcesManager from '../../store/ResourcesManager.mjs'
import FileManager from '../../store/FileManager.mjs'
import server from './server.mjs'

process.parentPort.on('message', (e) => {
  const [port] = e.ports

  const handleLogger = (type = 'info') => {
    return (data) => {
      if (!data) {
        return
      }
      const postData = {
        event: 'SERVER_LOG',
        level: type,
        message: ''
      }
      if (typeof data === 'string') {
        postData.message = data
      } else if (typeof data === 'object') {
        postData.message = JSON.stringify(data)
      }
      port.postMessage(postData)
    }
  }
  const logger = {
    info: handleLogger('info'),
    warn: handleLogger('warn'),
    error: handleLogger('error')
  }

  // 监听消息
  port.on('message', async (e) => {
    try {
      const { data } = e
      // 启动h5服务
      if (data.event === 'SERVER_START') {
        // 初始化数据库管理器
        dbManager = DatabaseManager.getInstance(logger)
        await dbManager.waitForInitialization()

        // 初始化各种管理器并等待它们初始化完成
        settingManager = SettingManager.getInstance(logger, dbManager)
        await settingManager.waitForInitialization()

        fileManager = FileManager.getInstance(logger, dbManager, settingManager)
        resourcesManager = ResourcesManager.getInstance(
          logger,
          dbManager,
          settingManager,
          fileManager
        )
        const serverRes = await server({
          dbManager,
          settingManager,
          resourcesManager,
          fileManager,
          logger,
          postMessage,
          onStartSuccess: (url) => {
            port.postMessage({
              event: 'SERVER_START::SUCCESS',
              url
            })
          },
          onStartFail: (data) => {
            port.postMessage({
              event: 'SERVER_START::FAIL',
              ...data
            })
          }
        })
        ioServer = serverRes.ioServer
      } else if (data.event === 'APP_SETTING_UPDATED') {
        await settingManager.getSettingData()
        // 广播设置更新给所有客户端
        ioServer?.emit('settingUpdated', {
          success: true,
          data: settingManager.settingData
        })
      }
    } catch (err) {
      logger.error(`[H5Server] ERROR => ${err}`)
    }
  })

  port.start()
})
2.4 文件服务子进程示例

功能说明: 文件服务子进程专门处理文件系统相关的操作,包括目录扫描、文件信息提取、图片质量计算等。这些操作通常比较耗时,放在子进程中执行可以避免阻塞主进程,提高应用的响应性。子进程支持批量处理和进度报告,能够高效处理大量文件。

主要功能:

  • 目录扫描:递归扫描指定目录下的所有文件
  • 文件信息提取:获取文件大小、创建时间、修改时间等
  • 图片质量计算:分析图片尺寸、格式、压缩率等
  • 批量处理:支持大量文件的批量处理,避免内存溢出
  • 进度报告:实时向主进程报告处理进度

性能优化:

  • 使用并行处理提高扫描效率
  • 分批处理避免内存溢出
  • 让出事件循环保持响应性
js:src/main/child_server/file_server/index.mjs 复制代码
/**
 * 文件服务子进程
 * */
import { readDirRecursive, calculateImageByPath } from '../../utils/utils.mjs'

process.parentPort.on('message', (e) => {
  const [port] = e.ports

  const handleLogger = (type = 'info') => {
    return (data) => {
      if (!data) {
        return
      }
      const postData = {
        event: 'SERVER_LOG',
        level: type,
        message: ''
      }
      if (typeof data === 'string') {
        postData.message = data
      } else if (typeof data === 'object') {
        postData.message = JSON.stringify(data)
      }
      port.postMessage(postData)
    }
  }
  const logger = {
    info: handleLogger('info'),
    warn: handleLogger('warn'),
    error: handleLogger('error')
  }

  // 监听消息
  port.on('message', async (e) => {
    const { data } = e

    // 分批处理大量文件
    const processBatch = async (files, batchSize = 1000) => {
      const results = []
      for (let i = 0; i < files.length; i += batchSize) {
        const batch = files.slice(i, i + batchSize)
        results.push(...batch)

        // 允许事件循环处理其他任务
        await new Promise((resolve) => setTimeout(resolve, 0))
      }
      return results
    }

    if (data.event === 'SERVER_START') {
      port.postMessage({
        event: 'SERVER_START::SUCCESS'
      })
    } else if (data.event === 'REFRESH_DIRECTORY') {
      const readDirTime = {
        start: Date.now(),
        end: Date.now()
      }
      try {
        // 获取现有文件列表(如果有)
        const existingFiles = data.existingFiles || []

        const fileMap = new Map()

        // 并行处理多个目录
        const dirPromises = data.folderPaths.map((folderPath) =>
          readDirRecursive(data.resourceName, folderPath, data.allowedFileExt, existingFiles)
        )
        // 等待所有目录处理完成
        const results = await Promise.all(dirPromises)

        // 合并结果
        for (const fileList of results) {
          if (fileList && fileList.length) {
            for (const item of fileList) {
              fileMap.set(item.filePath, item)
            }
          }
        }

        readDirTime.end = Date.now()

        // 添加统计信息
        const stats = {
          newFiles: fileMap.size,
          modifiedFiles: 0,
          totalProcessed: fileMap.size
        }

        // 对于大量文件,使用批量处理
        if (fileMap.size > 5000) {
          // 先发送一个处理中的消息
          port.postMessage({
            event: 'REFRESH_DIRECTORY::PROCESSING',
            isManual: data.isManual,
            resourceName: data.resourceName,
            totalFiles: fileMap.size,
            refreshDirStartTime: data.refreshDirStartTime,
            readDirTime
          })

          // 分批处理文件
          const batchedList = await processBatch([...fileMap.values()], 1000)

          // 发送最终结果
          port.postMessage({
            event: 'REFRESH_DIRECTORY::SUCCESS',
            isManual: data.isManual,
            resourceName: data.resourceName,
            list: batchedList,
            stats,
            refreshDirStartTime: data.refreshDirStartTime,
            readDirTime
          })
        } else {
          // 对于少量文件,直接发送
          port.postMessage({
            event: 'REFRESH_DIRECTORY::SUCCESS',
            isManual: data.isManual,
            resourceName: data.resourceName,
            list: [...fileMap.values()],
            stats,
            refreshDirStartTime: data.refreshDirStartTime,
            readDirTime
          })
        }
      } catch (err) {
        logger.error(`[FileServer] ERROR => 刷新资源目录失败: ${err}`)
        readDirTime.end = Date.now()
        port.postMessage({
          event: 'REFRESH_DIRECTORY::FAIL',
          isManual: data.isManual,
          resourceName: data.resourceName,
          list: [],
          refreshDirStartTime: data.refreshDirStartTime,
          readDirTime
        })
      }
    } else if (data.event === 'HANDLE_IMAGE_QUALITY') {
      try {
        const { list } = data
        const ret = []
        for (let i = 0; i < list.length; i++) {
          const imgData = await calculateImageByPath(list[i].filePath)
          ret.push({
            id: list[i].id,
            ...imgData
          })
        }
        port.postMessage({
          event: 'HANDLE_IMAGE_QUALITY::SUCCESS',
          resourceName: data.resourceName,
          list: ret
        })
      } catch (err) {
        logger.error(`[FileServer] ERROR => 处理图片质量失败: ${err}`)
        port.postMessage({
          event: 'HANDLE_IMAGE_QUALITY::FAIL',
          resourceName: data.resourceName,
          list: []
        })
      }
    }
  })

  port.start()
})
2.5 主进程处理子进程消息

功能说明: 主进程接收和处理来自子进程的消息,包括处理结果、状态更新、错误信息等。通过消息处理机制,主进程可以获取子进程的执行结果,更新应用状态,并向用户界面发送相应的通知。这种机制确保了主进程能够及时响应用户操作和系统事件。

消息类型:

  • 成功消息:处理完成,包含结果数据
  • 失败消息:处理失败,包含错误信息
  • 进度消息:处理进度更新
  • 日志消息:子进程的日志信息

处理流程:

  • 根据消息类型执行不同的处理逻辑
  • 更新内存中的资源映射
  • 向用户界面发送通知
  • 记录处理时间和性能指标
js:src/main/store/index.mjs 复制代码
// 处理文件服务子进程启动
handleFileServerStart() {
  try {
    // 启动子进程
    this.fileServer?.start({
      onMessage: ({ data }) => {
        switch (data.event) {
          case 'REFRESH_DIRECTORY::SUCCESS':
            // 添加接收时间戳
            data.receiveMsgTime = Date.now()
            this.onRefreshDirectorySuccess(data)
            break
          case 'REFRESH_DIRECTORY::FAIL':
            // 添加接收时间戳
            data.receiveMsgTime = Date.now()
            this.onRefreshDirectoryFail(data)
            break
          case 'SERVER_LOG': {
            const type = data.level
            if (type && typeof global.logger[type] === 'function') {
              global.logger[type](data.message)
            } else {
              global.logger.info(`[FileServer] INFO => ${data.message}`)
            }
            break
          }
          case 'HANDLE_IMAGE_QUALITY::SUCCESS':
            this.fileManager.onHandleImageQualitySuccess(data, this.locks)
            break
          case 'HANDLE_IMAGE_QUALITY::FAIL':
            this.fileManager.onHandleImageQualityFail(this.locks)
            break
        }
      }
    })
  } catch (err) {
    global.logger.error(err)
  }
}

// 文件服务子进程-遍历目录完成
onRefreshDirectorySuccess(data) {
  // 获取开始时间和接收时间,使用当前时间作为默认值而不是0
  const startTime = data.refreshDirStartTime || Date.now()
  const receiveTime = data.receiveMsgTime || Date.now()
  const processTime = receiveTime - startTime

  global.logger.info(
    `[FileServer] SUCCESS => 刷新资源目录完成: ${data.resourceName}, 文件数量: ${data.list.length}, 处理时间: ${processTime}ms`
  )

  // 更新资源映射
  this.resourcesManager.updateResourceMap(data.resourceName, data.list)

  // 发送消息到主窗口
  if (global.FBW.mainWindow.win) {
    global.FBW.sendCommonData(global.FBW.mainWindow.win)
    global.FBW.sendMsg(global.FBW.mainWindow.win, {
      type: 'success',
      message: t('messages.refreshDirectorySuccess', {
        resourceName: data.resourceName,
        count: data.list.length
      })
    })
  }
}

3. 通信特点

3.1 使用 Electron Utility Process

飞鸟壁纸使用 Electron 的 utilityProcess 来创建子进程,这是 Electron 推荐的安全方式。

技术优势:

  • 安全性utilityProcess 提供了更好的安全隔离
  • 性能 :比传统的 child_process 更高效
  • 稳定性:更好的错误处理和资源管理

使用场景:

  • 需要执行耗时操作的场景
  • 需要隔离运行的服务
  • 需要高安全性的数据处理
  • 需要独立资源管理的任务
3.2 消息通道通信

使用 MessageChannelMain 进行进程间通信。

功能说明: 消息通道是主进程与子进程之间通信的核心机制。通过创建消息通道,主进程和子进程可以建立双向通信链路,实现高效的数据传输和事件传递。消息通道支持传递各种类型的数据,包括对象、数组、甚至可转移对象。

通信特点:

  • 双向通信:主进程和子进程都可以发送和接收消息
  • 异步处理:消息处理是异步的,不会阻塞进程执行
  • 类型安全:支持传递复杂的数据结构
  • 可转移对象:支持传递端口等可转移对象
js 复制代码
const { port1, port2 } = new MessageChannelMain()
this.#child = utilityProcess.fork(this.#serverPath, options)
this.#port2 = port2

this.#port2.on('message', onMessage)
this.#port2.start()
3.3 事件驱动架构

子进程通过事件驱动的方式处理消息。

功能说明: 事件驱动架构是子进程处理消息的核心模式。通过监听消息事件,子进程可以根据消息类型执行相应的处理逻辑。这种架构具有良好的扩展性,可以轻松添加新的消息类型和处理逻辑,同时保持代码的清晰和可维护性。

设计优势:

  • 解耦合:消息发送和处理逻辑分离
  • 可扩展:易于添加新的消息类型
  • 可维护:代码结构清晰,易于理解和维护
  • 异步处理:支持异步操作,不会阻塞消息接收
js 复制代码
port.on('message', async (e) => {
  const { data } = e

  switch (data.event) {
    case 'SERVER_START':
      // 处理服务启动
      break
    case 'REFRESH_DIRECTORY':
      // 处理目录刷新
      break
    case 'HANDLE_IMAGE_QUALITY':
      // 处理图片质量计算
      break
  }
})

通信模式总结

1. 主进程 ↔ 渲染进程

  • 通信方式:IPC (Inter-Process Communication)
  • 技术实现contextBridge + ipcRenderer + ipcMain
  • 特点
    • 安全性高:通过预加载脚本控制 API 暴露
    • 性能好:异步通信,不阻塞 UI
    • 易用性强:提供统一的 API 接口

2. 主进程 ↔ 子进程

  • 通信方式:Electron Utility Process IPC
  • 技术实现utilityProcess.fork + MessageChannelMain
  • 特点
    • 安全性高:使用 Electron 推荐的 Utility Process
    • 隔离性好:子进程独立运行,崩溃不影响主进程
    • 性能优:比传统 child_process 更高效

3. 使用场景

3.1 主进程 ↔ 渲染进程
  • 用户界面交互
  • 设置数据同步
  • 文件操作
  • 窗口控制
3.2 主进程 ↔ 子进程
  • H5 服务:提供本地 HTTP/HTTPS 服务,支持 Socket.IO 实时通信
  • 文件服务:处理大量文件扫描、图片质量计算等耗时操作
  • 数据处理:避免阻塞主进程的复杂计算任务

总结

飞鸟壁纸的进程通信架构通过不同的通信机制实现了主进程、渲染进程和子进程之间的高效协作:

  1. 主进程与渲染进程 :通过预加载脚本实现安全的 IPC 通信,使用 contextBridgeipcRenderer
  2. 主进程与子进程 :通过 Electron Utility Process 实现服务隔离,使用 utilityProcess.forkMessageChannelMain

这种设计确保了应用的安全性、稳定性和可扩展性,为不同场景下的进程间通信提供了合适的解决方案。特别是使用 Electron 推荐的 Utility Process 技术,提供了更好的安全性和性能。

相关推荐
ywf12151 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭1 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq8 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常10 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端