系列文章目录
- vue3+electron开发桌面软件入门与实战(0)——创建electron应用
- vue3+electron开发桌面软件入门与实战(1)——创建electron+vue3主体项目
- vue3+electron开发桌面软件入门与实战(2)——创建electron+vue3项目级集成
- vue3+electron开发桌面软件入门与实战(3)—— electron模块化改造+窗口位置工具+合并命令行工具
- 后续文章请关注专栏
前言
前面的文章,为了复现问题,我和大家一起从零重新构建的一个项目,各类代码和截图复原的也是原汁原味的构建过程,以方便引领大家顺利入门。
如今我已开源代码,需要的可私信我获取。基于此,后面的知识点和代码会挑拣重点来展示。如果你从零开始,那么按照本篇文章之前展示的代码,可以完整无误地实现效果。而按照以后的代码和讲解,可以为你打通主要关节,但因为去除部分细枝末节的代码,可能无法保证基础略薄弱的同学直接实现效果。所以如果在阅读本系列的过程中,遇到问题,建议参考源码。
这样做也有好处,前面的文章动辄大几千字,其实很影响阅读,后面的文章则会更简练。
回顾前文,我们讲解了如果简单地实现vue3+electron项目运行,如何合理地通过vite构建打包架构,对electron的一些简单优化,本篇文章将讲解electron的进程、托盘实现、项目内右键(不同于系统级右键)等实现。
一、electron的进程
electron有个很重要的概念就是进程和进程间通信,科学详细的描述请查阅官网,这里说下简单的理解:
electron分为主进程和渲染进程,主进程负责和系统级数据交互,所以需要借助nodejs或者jdk等环境,渲染进程主要负责渲染页面展示,所以需要借助浏览器环境。
正常情况下,出于安全考虑,浏览器是不可以直接操控系统底层数据的,否则咱们随便浏览个网页,就被人家植入木马或者读取电脑的关键数据了。而electron的进程通信,就是提供一种安全的通道,让浏览器环境中的渲染进程可以访问node环境中的主进程。这个通道,在electorn中被称为IPC通道。
其实我们不用特别地深究进程通信的概念,进程通信听着很麻烦,也只能说明它的实现源码可能会不太简单,但是作为一个成熟的框架,进程通信在我们的代码中使用,却极为简单。
主进程是main.js这毫无疑问,前端页面和主进程的通信需要通过预加载脚本preload.js,预加载脚本就是一个桥梁。
主进程是咋知道谁是预加载脚本的?其实前面文章已经写过了配置,只不过没有展开讲。
调用BrowserWindow方法创建窗口时,定义的参数webPreferences.preload就是预加载脚本的入口配置:
webPreferences: {
// nodeIntegration:true, //集成node api
// contextIsolation:false //关闭上下文隔离,配合nodeIntegration,可以赋予在render进程中写node代码的能力
preload: path.resolve(__dirname, '../preload/preload.js') //预加载的js文件
}
注释的配置是暴力关闭上下文隔离以达到通信的目的,安全隐患太大,不建议。最好是使用preload参数配置预加载脚本的地址。
二、进程通信代码实现
按照官网讲述,electron进程通信一共分为四种模式:
- 渲染器进程到主进程(单向)
- 渲染器进程到主进程(双向)
- 主进程到渲染器进程
- 渲染器进程到渲染器进程
按照经验,第二种模式使用的场景更多,所以在我们需要通信的时候,如果无法确定应该使用哪种模式,就首选第二种模式。
我们以第二种模式和第三种模式为例:
1.渲染器进程到主进程(双向)
网络上大部分教程都是借鉴或者直接把官网文档抄过来,所以导致讲述流程都是正向的,就像做数学题一样,我事先已经知道了参考答案,所以从头给你讲,听完你觉得好有道理。下一道类似习题发现还是不会。
官网这样讲解没有问题,因为它是作为参考文档,方便我们在遇到问题的时候去查阅。但我们真正去学的时候,这样会很累。
有时候我们需要从问题反推,才能复现我们真正遇到问题的场景。
我们经常遇到的一种场景是:页面上有个操作按钮,点击按钮后,通过node操作系统。这就涉及如何把操作指令从渲染进程发送到主进程。
我们的起点在页面按钮上,所以点击按钮后应该有个方法去通知主进程,形如:
myApi.handleGetProgressArgv().then((value)=>{
console.log(value)
})
通过这个方法,我们就能通知主进程你该调用某个特定函数了!!!
- myApi:自定义的对象,需要在预加载js中定义,后续介绍。
- handleGetProgressArgv:自定义的函数,需要在预加载js中定义,后续介绍。
可以看到,这个方法完全是自定义的一个方法,如果不作任何操作,系统是绝对不认识的。所以我们需要给我们想要的方法一个定义,让系统知道,这个渲染器里调用的方法,我定义的。
渲染页面调用主进程是有危险的,所以这脏活累活肯定得桥梁preload.js来做:
const handleGetProgressArgv=async () => {
let files = await ipcRenderer.invoke('get-progress-files')
console.log('files',files)
return files // 返回结果
}
/**
* 暴露给前端windows对象,windows对象的属性可以用windows.myApi调用或者直接简写为myApi调用
* */
contextBridge.exposeInMainWorld('myApi', {
handleGetProgressArgv
})
preload.js中定义函数并且把它通过exposeInMainWorld暴露,页面就能自动找到这个函数了。
现在页面通过按钮,能直接给preload.js发指令调用某个函数了,但是preload.js支持的node模块是很有限的,它毕竟和渲染页面不清不楚的,不太安(干)全(净)啊。
所以预加载页面拿到这个指令后,还得继续向上报告。这就是下面这行代码做的事。
ipcRenderer.invoke('get-progress-files','hhhhhhhhhhhh')
这句代码告诉主进程,我这有个方法叫“get-progress-files”,参数是‘hhhhhhhhhhhh’,大哥帮忙接受下。
可预加载里方法多了去了,主进程怎么分辨出来哪个函数做什么?所以主进程自己也搞了一个api叫‘handle’,用来和预加载里的函数一一对应:
ipcMain.handle('get-progress-files',(event,msg)=>{
……省略一堆代码
console.log(msg) //这里会打印一串hhhhhhhhhhhh
return 'main'
})
主进程又可以返回一直值:‘main’,这个值会被预加载里对应的函数接收,我们赋值给files,形如:
let files = await ipcRenderer.invoke('get-progress-files')
然后预加载返回的值,又能被页面中的方法“myApi.handleGetProgressArgv”接收,至此,一个指令带着参数,由页面开始,流转一圈后,又带着返回值回到页面了。
因为这种方式,可以说是把流程走了一个来回,所以应用范围最广。
2.主进程到渲染器进程
我们在项目中还经常遇到一种情况,工具栏需要控制渲染器的行为,比如返回登录页、打开调试工具等等。如图:
这就是典型的主进程到渲染器进程通信。主进程发送指令,控制渲染器进行相应操作。
原理其实和上面双向通信类似。
指令从主进程开始,所以在点击工具栏按钮时,发送指令:
{
label: '文件',
submenu: [
{
label: '返回首页',
accelerator: 'CmdOrCtrl+W',
click:() => this.win.webContents.send('go-page', 'login'), //主进程主动通信到渲染进程
},
{
label: '退出',
accelerator: 'CmdOrCtrl+Q',
click() {
app.quit()
}
}
]
},
当点击【返回首页】时,会告诉预加载脚本,我给你发了个函数:
预加载脚本为了能正确接到对应的函数,需要对应着接一下,不同于上面的invoke接口,这次需要用“on”来接住:
const handleGoPage=(callback) => ipcRenderer.on('go-page', callback) //监听主进程事件
预加载脚本接住后想告诉页面主进程又来活了,就只能继续暴露这个函数:
contextBridge.exposeInMainWorld('myApi', {
handleGoPage,
handleGetProgressArgv
})
页面上找个恰当的时机,我放在了App.vue中,调用这个函数:
myApi.handleGoPage((event, value) => {
router.push(value)
// event.sender.send('counter-value', value) //将回复结果发回主进程
})
这样,就完成了主进程传达的指令。
3.进程通信思考
另外两种进程通信方式这里不再赘述。
通过这两种通信方式,不难总结规律:
主进程 <——> 预加载脚本 <——> 渲染页面
进程通信就是这三者之间互相调用,通过预加载脚本这个桥梁,让主进程和渲染进程可以互相发送指令。进程通信涉及的api对于新接触的同学来说,可能比较陌生,不过理解了这个规律,这些api也不过是成对出现的发送和接收接口而已。
总结
这篇文章用相对通俗的语言解释了electron的进程通信,并为其中的两种编写示例代码。如果能够理解主进程、预加载脚本、渲染页面之间的关系,其实不管有多少种进程通信方式,也不过是多查阅几个api而已。