初识Electron 进程通信

概述

Electron = chromium + nodejs + native API,也就是将node环境和浏览器环境整合到了一起,这样就构成了桌面端(chromium负责渲染、node负责操作系统API等)

流程模型

预加载脚本:运行在浏览器环境下,但是也能访问部分node API
在这里插入图片描述

初始化项目

先创建一个vue3+vite的项目,然后在项目当中安装electron。具体命令就是这样

npm create vite@latest vue-electron
cd vue-electron
npm i
npm i electron -D

构建Electron项目

在package.json文件当中修改和添加type和main配置,electron脚本运行在node环境下,所以type要设置为commonjs,然后main指向electron运行的入口文件,再将启动命令加进去,这样就可以通过 npm run start 来启动electron座面应用了

  "type": "commonjs","main": "electron/main/index.js"// 启动命令"start": "electron .",

编写electron/main/index.js。通过BrowserWindow创建一个浏览器窗口,然后可以通过loadURL加载一个网页,或者通过loadFile加载一个html文件进行渲染。然后还可以通过初始化监听去创建对应的窗口和关闭窗口的时候去退出electron。

const { app, BrowserWindow } = require('electron')
const createWindow = () => {const win = new BrowserWindow({width: 800,height: 600})win.loadURL('http://localhost:5173/');// win.loadFile('index.html')}
app.whenReady().then(() => {createWindow()app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})
app.on("window-all-closed", () => {console.log("在关闭一个应用的所有窗口后让它退出");// 在关闭一个应用的所有窗口后让它退出if (process.platform === "darwin") {app.quit();}
});

github 仓库地址:https://github.com/lizuoqun/vue-electron.git

窗口常用方法

在win【BrowserWindow】对象当中会有一些常用的方法,这里简单列出来一下,完整的在这BrowserWindow Object API

  • getAllWindows:所有打开的窗口的数组
  • getFocusedWindow:此应用程序中当前获得焦点的窗口
  • fromBrowserView:通过给定的视图返回对应的窗口
  • close:关闭窗口
  • maximize:最大化窗口
  • unmaximize:取消窗口最大化
  • 还有最小化、全屏、聚焦、背景、阴影、透明等等,用的时候可以再去文档里面找一下对应的方法调用

命令合并

在这里是vue3和electron项目都在一起,并且是直接访问5137端口加到桌面端的,这个时候可以通过 npm-run-all 这个包可以将命令进行合并。先npm install npm-run-all这个包,然后改一下下面的启动脚本,通过 npm run deva 就可以直接启动vue3和electron项目了。

{"scripts": {"dev": "vite","build": "vue-tsc -b && vite build","preview": "vite preview","start": "electron .","deva": "npm-run-all --parallel dev start"},
}

预加载脚本

预加载脚本通过进行配置,在electron入口文件中进行配置,这里配置了一个同目录下的preload作为先进行加载的js脚本。

其中对于 WebPreferences Object 对象的配置可以看这个官方文档的配置,其中有几个比较常用的

  • devTools boolean 是否开启 DevTools
  • preload string 预加载的脚本文件地址
  • nodeIntegration boolean 是否启用Node integration 是否可以使用 Node.js 功能的配置
  • contextIsolation boolean 是否在独立 JavaScript 环境中运行 Electron API和指定的preload 脚本. 默认为 true
    • nodeIntegration设置为true后require is not define。需要设置contextIsolation为false
  const win = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js')}});

扩展

这里来一个简单的electron当中的预加载脚本看一下并且进行扩展下

contextBridge API 上下文桥梁,其他更多详细的配置可以参考官方文档,下面只说一下使用到的

在隔离的上下文中创建一个安全的、双向的、同步的桥梁。

exposeInMainWorld方法

contextBridge.exposeInMainWorld(apiKey, api)

  • apiKey string - 将 API 注入到 窗口 的键。 API 将可通过 window[apiKey] 访问。
  • api any - 你的 API可以是什么样的以及它是如何工作的相关信息如下。
const { contextBridge } = require('electron')
/*** 在预处理的过程当中,不能直接将值给绑定到window对象上再取值,* 虽然预加载脚本与其所附着的渲染器在共享着一个全局 window 对象,* 但并不能从中直接附加任何变动到 window 之上,因为 contextIsolation 是默认的。* 所以在preload当中window.myAPI = { desktop: true } 设置了myAPI,但是在render当中获取不到console.log(window.myAPI)*/
contextBridge.exposeInMainWorld('versions', {node: () => process.versions.node,chrome: () => process.versions.chrome,electron: () => process.versions.electron,
})

因为这个脚本是预加载的,在项目启动之后就会执行,并且他里面的数据都是直接绑定在window对象上,那么在这个项目里面其他的地方就可以直接使用了,比方说在vue代码当中使用:

这里不能通过模版插值直接写的,原因很简单,在vue里面只会去加载setup里面的数据,但是versions是window的,所以要在js里面用

<script setup lang="ts">
import {onMounted} from 'vue';onMounted(() => {const information = document.getElementById('info') as HTMLDivElement;information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`;
});
</script><template><div id="info"></div><div>versions: {{ versions }}</div>
</template>

进程通信

概述

Electron 继承了来自 Chromium 的多进程架构,网页浏览器的基本架构是单个浏览器进程控制不同标签页进程,以及整个应用程序的生命周期。这样可以避免单个浏览器的无响应不会影响到整个浏览器。

Electron 应用的大致工作流程是:启动APP——主进程创建window——win加载页面(渲染进程)

Electron 应用程序将控制两种类型的进程:主进程和渲染器进程。

主进程&渲染进程是啥

主进程 ipcMain

  • 是应用的核心,在应用启动时运行,并在整个应用的生命周期中保持活动状态
  • 主要功能
    • 创建和管理应用程序窗口
    • 管理窗口生命周期
    • 使用原生API

渲染进程

  • 是应用的用户界面部分,渲染线程运行在 Chromium 内核中,可以像 Web 浏览器一样加载和呈现网页
  • 主要功能
    • 一个应用可以有多个渲染进程
    • Windows 中展示的界面通过渲染进程表现
    • 每个窗口都是一个独立的渲染线程
    • 无法直接操作原生API

渲染进程到主进程(单向通信)

在这里借用预加载脚本进行通信,这里演示一下直接以vue3页面作为渲染进程,然后通过preload预加载脚本传递到electron入口也就是主进程。

<script setup lang="ts">
const renderToMainProcess = () => {electronApi.setTitle('渲染进程');
};
</script>

然后这个方法是在预加载脚本preload.js通过exposeInMainWorld进行绑定在window对象上的,所以在上面可以直接使用

contextBridge.exposeInMainWorld('electronApi', {// 渲染器进程到主进程的通信(单向通信)setTitle: (title) => ipcRenderer.send('set-title', title)
});

在最后可以在electron入口当中拿到这个数据。这里就有问题了,这是怎么知道触发了setTitle方法?

ipcMain:API在这

ipcRenderer:是一个 EventEmitter 的实例。 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。

在ipcMain当中可以通过on、once等方法来监听ipcRenderer发送的事件。这就类比一个bus事件总线,从ipcRenderer发的set-title事件之后,从ipcMain当中拦截监听这个事件,并且指定对应的回调来处理后续操作。

  • event:他是一个 IpcMainEvent 对象。在这个可以直接通过点去获取里面的属性,其中sender WebContent - 返回发送消息的 webContents
  • 然后通过 fromWebContents() 方法找到对应的窗口对象,最后通过setTitle设置标题
  • 补充:直接用win.setTitle其实也是一样的效果,这是因为这个渲染进程和主进程是在同一个页面上的,所以效果其实是一样的。因为 win 对象是在创建窗口时定义的,它代表了整个窗口,而不是特定的渲染进程。而我们需要的是找到发送消息的特定渲染进程对应的窗口对象,
const createWindow = () => {const win = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js')}});ipcMain.on('set-title', (event, title) => {const webContents = event.sender;const ipcWin = BrowserWindow.fromWebContents(webContents);ipcWin.setTitle(title);// win.setTitle(title)});win.loadURL('http://localhost:5173/');
};

渲染进程到主进程(双向通信)

在主进程(入口)当中添加以下代码:这里模拟的效果是在vue当中点击按钮后通过electron主进程打开文件系统选择文件,选择文件之后拿到文件的路径之后再传递给渲染进程(vue)进行显示。

dialog API在这

  • 显示用于打开和保存文件、警报等的本机系统对话框
  • showOpenDialog打开这是一个异步的所以给他加了async和await变成同步,他对应的也有一个showOpenDialogSync同步方法。
  • Promise成功包含的数据
    • canceled boolean - 对话框是否被取消。
    • filePaths string[] - 用户选择的文件路径的数组. 如果对话框被取消,这将是一个空的数组。因为前面调用showOpenDialog方法的时候没有给他设置multiSelections参数(文件多选),这里直接取数组下标0也就是文件的路径了。
  • ipcMain.handle:为一个invokeable的IPC 添加一个handler。 每当一个渲染进程调用 ipcRenderer.invoke(channel, ...args) 时这个处理器就会被调用。
async function handleFileOpen () {const { canceled, filePaths } = await dialog.showOpenDialog({})if (!canceled) {return filePaths[0]}
}app.whenReady().then(() => {ipcMain.handle('dialog:openFile', handleFileOpen)createWindow();app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) createWindow();});
});

在预加载脚本当中把dialog:openFile给定义下。这里用的是invoke:表示通过 channel 向主过程发送消息,并异步等待结果。拿到这一步就实现了主进程和渲染进程的双向通信机制,和单向通信进行对比的区别在于单向通信使用到的send和on是单向传递(理解成on和emit就可以了,只能单向传递)。而双向通信通过invoke和handle就像是一个监听和异步等待的效果,每一个事件都会在ipcRenderer当中异步等待响应结果,得到结果之后直接响应给渲染进程也就完成了双向通信

contextBridge.exposeInMainWorld('electronApi', {// 渲染器进程到主进程的通信(双向通信)openFile: () => ipcRenderer.invoke('dialog:openFile')
});

最后就是在vue渲染进程当中进行触发

<script setup lang="ts">
import {ref} from 'vue';const renderToMainSingleProcess = () => {electronApi.setTitle('渲染进程');
};let filePath = ref('')
const renderToMainMultiProcess = async () => {filePath.value = await window.electronApi.openFile()
};
</script><template><div>渲染进程->主进程通信</div><el-button type="primary" @click="renderToMainSingleProcess">单向</el-button><el-button type="primary" @click="renderToMainMultiProcess">双向</el-button><div>双向通信的文件路径为:{{ filePath }}</div>
</template>

主进程到渲染进程

在electron主进程当中添加一个menu菜单项,这里通过webContents.send发送消息

  const menu = Menu.buildFromTemplate([{label: app.name,submenu: [{click: () => win.webContents.send('update-counter', 1),label: '加一'},{click: () => win.webContents.send('update-counter', -1),label: '减一'}]}]);Menu.setApplicationMenu(menu);

预加载脚本暴露

contextBridge.exposeInMainWorld('electronApi', {// 主进程向渲染器进程发送消息onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),counterValue: (value) => ipcRenderer.send('counter-value', value)
});

最后在渲染进程当中调用即可实现效果,在这里的onUpdateCounter相对于一个回调触发,在主进程当中调用update-counter之后会通过预加载暴露的方法到渲染进程,这个渲染进程可以理解成一个监听器,得到监听结果之后就可以拿到改变的值了。

补充:然后这里的渲染进程和主进程的通信看前面的就好了,也就是预加载脚本通过send发送,在主进程当中通过ipcMain.on得到发送的消息

<script setup lang="ts">
import {ref} from 'vue';let counter = ref(0);
const mainToRender = () => {window.electronApi.onUpdateCounter((value) => {const oldValue = Number(counter.value);const newValue = oldValue + value;counter.value = newValue.toString();window.electronApi.counterValue(newValue);});
};
</script><template><el-button type="primary" @click="mainToRender">主进程->渲染进程通信</el-button><div>Current value = {{ counter }}</div>
</template>

遇见的问题

问题一:-32601

开启了控制台报错:win.webContents.openDevTools();

{"code":-32601,"message":"'Autofill.enable' wasn't found"}

异常信息:在打开了控制台的时候会报这个错误,但是并不影响实际使用:这个持续可以看一下Electron GitHub issues

问题二:Authors is required
An unhandled rejection has occurred inside Forge:
Error: Failed with exit code: 1
Output:
���ڳ��Դӡ�web_electron.nuspec�����ɳ������
Authors is required.
Description is required.

可以看到这个的报错是Authors和Description都是必填的,这个在package.json文件当中可能在初始化项目时没有填这两项,这两项是必填的,补充一下即可

打包之后在out目录下会有改exe文件,exe文件名对应的是项目名称,out\web-electron-win32-x64到这里也就完成了electron的打包了,这个exe文件就可以直接在电脑上打开了。

打包

electron-build打包

这里安装electron-builder,需要安装在开发环境当中

npm i electron-builder -D

而后在package.json里面添加build配置,这里简化些,下面是需要添加的打包配置。打包后在dist目录下的exe直接安装即可

{"scripts": {"build-win": "electron-builder"},"build": {"productName": "Electron","appId": "com.modify","copyright": "by modify","asar": true,"win": {"icon": "electron/logo.ico"},"nsis": {"oneClick": false,"allowElevation": true,"allowToChangeInstallationDirectory": true,"installerIcon": "electron/logo.ico","uninstallerIcon": "electron/logo.ico","installerHeaderIcon": "electron/logo.ico","createDesktopShortcut": true,"createStartMenuShortcut": true,"shortcutName": "APP","runAfterFinish": true}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/10355.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

建网站怎么建?只需几个步骤

在这个网络飞速发展的时代&#xff0c;越来越多的人都渴望拥有自己的网站。然而&#xff0c;对于大多数新手来说&#xff0c;如何建立自己的网站可能充满了挑战。本文将为您详细介绍建网站的关键步骤&#xff0c;让您能够轻松搭建自己的网站。 选择适合的建站工具 虽然市面上有…

台达控制器与三菱变频器实现EtherCAT转CC-Link IEFB协议通讯方案

一.项目背景&#xff1a; 在某自动化生产车间中&#xff0c;原有系统采用台达的 EtherCAT 控制器来控制多个设备的运动和操作&#xff0c;但车间内的一些关键设备使用的是三菱变频器&#xff0c;且基于 CC-Link IEFB 协议通讯。为了实现整个系统的集中控制和数据统一管理&#…

Js — 防抖及底层实现

防抖&#xff1a;单位时间内&#xff0c;频繁触发事件&#xff0c;只执行最后一次 防抖实现方式&#xff1a; lodash提供的防抖函数_.debounce(func,[wait0],[option]) 延迟wait毫秒后调用func方法 定时器setTimeout 目标&#xff1a;鼠标在盒子上移动&#xff0c;鼠标停止50…

负载均衡式在线oj项目开发文档2(个人项目)

judge模块的框架 完成了网页渲染的功能之后&#xff0c;就需要判断用户提交的代码是否是正确的&#xff0c;当用户点击提交之后&#xff0c;就会交给路由模块的/judge模块&#xff0c;然后这个路由模块就需要去调用jude模块了&#xff0c;也就是需要一个新的jude模块&#xff…

setContentView调用流程(二) -将布局添加到mContentParent

Android setContentView执行流程(一)-生成DecorView Android setContentView执行流程(二)-将布局添加到mContentParent 上篇博客我们介绍了setContentView的第一步即生成DecorView以及获取到mContentParent的流程&#xff0c;同时还提到继承自Activity和AppCompatActivity生成…

【C#设计模式(2)——工厂模式】

前言 工厂模式&#xff1a;使用工厂创建对象。工厂模式的主要目的是分离对象的创建与调用&#xff0c;通过使用工厂统一管理对象的创建。工厂模式可以隐藏对象的创建细节&#xff0c;使客户终端代码只关注使用对象而不需要关注对象的创建过程。 运行结果 代码 #region 食品 /…

Dockerfile

1. Dockerfile 简介 1.1 什么是Dockerfile Dockerfile是一个用于定义和构建Docker镜像的文本文件&#xff0c;它通过一系列指令和参数来描述镜像的构建过程和配置。这些指令包括基础镜像、软件包安装、文件拷贝、环境变量设置等&#xff0c;使得应用程序及其依赖项可以被打包…

VBA高级应用30例应用3在Excel中的ListObject对象:插入行和列

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

C++OJ_二叉树的层序遍历

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C_OJ 小伞的主页&#xff1a;xiaosan_blog 二叉树的层序遍历 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff0…

ctfshow-web入门-反序列化(web265-web270)

目录 1、web265 2、web266 3、web267 4、web268 5、web269 6、web270 1、web265 很简单的一个判断&#xff0c;满足 $this->token$this->password; 即可 由于 $ctfshow->tokenmd5(mt_rand()) 会将 token 随机为一个 md5 值&#xff0c;我们使用 & 绕一下&am…

【STL】queue,stack的底层实现

在前面的介绍中我们已经知道了queue和stack是一个容器适配器&#xff0c;它并没有被划分到容器的行列&#xff0c;它只是对其他容器的再封装&#xff0c;在STL中queue和stack默认使用的容器是deque 在数据结构的学习中&#xff0c;我们知道stack和queue可以使用顺序表和链表实现…

Tomcat安装和配置(超详细)

一、Tomcat安装准备 1、tomcat下载 1.1、百度网盘链接下载 链接&#xff1a;https://pan.baidu.com/s/1uceOKe_QcpSQ6yhNxi4T5g?pwd1234 提取码&#xff1a;1234 1.2、官网在线下载 Tomcat官网&#xff1a;https://tomcat.apache.org/download-80.cg…

Ozone调试WSL系统的STM32编译文件配置

文章目录 背景步骤Ozone新建工程流程配置Ozeon找到WSL的代码文件ozone字体调整快速在Ozone中定位到代码文件参考 背景 在使用WSL进行嵌入式软件开发的时候&#xff0c;在debug方面&#xff0c;比较好用的工具有Ozone&#xff0c;那在Windows下调试需要配置和注意的点&#xff…

洛谷 P2239 [NOIP2014 普及组] 螺旋矩阵

本文由Jzwalliser原创&#xff0c;发布在CSDN平台上&#xff0c;遵循CC 4.0 BY-SA协议。 因此&#xff0c;若需转载/引用本文&#xff0c;请注明作者并附原文链接&#xff0c;且禁止删除/修改本段文字。 违者必究&#xff0c;谢谢配合。 个人主页&#xff1a;blog.csdn.net/jzw…

python通过usb连接标签打印机-开源的

背景&#xff1a; 最近接到了一个新需求&#xff0c;单位想做一个ERP系统&#xff0c;想把打印机一起兼容进去&#xff0c;实现自动化打印工作。主要我是做爬虫的没接触过这些&#xff0c;就到网上搜索了很多先关资料&#xff0c;最终发现&#xff0c;一大堆全都是什么VIP的才能…

Codeforces Round 984 (Div. 3)

题目链接 A. Quintomania 题意 思路 模拟即可 示例代码 void solve() {int n;cin >> n;vector<int>arr(n);fer(i, 0 ,n) cin >> arr[i];fer(i, 1, n){if(abs(arr[i] - arr[i - 1]) ! 5 && abs(arr[i] - arr[i - 1]) ! 7){cout << "N…

【2】GD32H7xx 串口Idle + DMA接收不定长数据

目录 1. IDLE中断相关介绍2. D-Cache与DMA同时使用2.1 I-Cache与D-Cache2.2 D-Cache与DMA同时使用时的数据一致性问题2.2.1 CPU读取DMA写入到SRAM的数据2.2.2 DMA读取CPU写入到SRAM的数据 3. Uart Idle DMA收发程序4. 程序测试 1. IDLE中断相关介绍 在 GD32H7xx MCU 中&#…

python数据结构基础(8)

今天来使用python实现二叉树,二叉树中每个节点都是Node类对象,通过Tree类中的add()方法逐个向二叉树中加入树节点,构成完全二叉树或者非完全二叉树,代码如下: class Node(object):"""树节点类&#xff0c;用于构建二叉树。Attributes:- val: 节点存储的值。- r…

IEEE 1588:电信网络的精确时间协议 (PTP)

IEEE 1588&#xff1a;电信网络的精确时间协议 IEEE 1588 PTP 概述PTP 协议特征同步类型IEEE 1588 PTP 角色IEEE 1588 PTP 的工作原理PTP 设备类型PTP 消息类型事件消息一般信息 PTP 时钟类规范PTP 配置文件 https://www.techplayon.com/ieee-1588-precision-time-protocol-ptp…

深度学习基础—了解词嵌入

引言 上图是使用one-hot向量表示词向量的一种方式&#xff0c;这种表示方式优点是方面简洁&#xff0c;但是缺点也很明显&#xff0c;就是词与词之间独立性太强&#xff0c;没有关联&#xff0c;这样使得算法对相关词的泛化能力不强。 举个例子&#xff0c;假如我们已经学习到了…