46

【Electron】酷家乐客户端开发实践分享 — 进程通信

 5 years ago
source link: https://www.tuicool.com/articles/3YnAvuF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

Electron中的进程,其实就是计算机中的进程,我们先来看看什么是进程通信

进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法

每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。

一个Electron应用有一个主进程和多个渲染进程,渲染进程还可能内嵌多个webview。两两之间都可能需要进行通信,情况还是比较复杂的。

需要通信的对象

主进程: 使用 ipcMain 进行通信

渲染进程: 使用 ipcRendererremote 模块进行通信

webview: 一般会禁用webview的node集成,然后使用preload的方式拿到 ipcRenderer 来做进程通信。

// preload.js

const electron = require('electron');

const { ipcRenderer } = electron;

// 把ipcRenderer挂载到window上,webview内部的js可以拿到这个模块
window.ElectronIpcRenderer = ipcRenderer;

ipcRenderer/ipcMain VS remote

主进程和渲染进程通信方式,拧出来单独说一下。先来看一个简单例子的:

点击创建按钮,创建一个新的窗口。点击关闭按钮,关掉这个新窗口。

nu2iEvE.png!web

左侧代码使用 ipcRenderer/ipcMain 进行通信,右侧代码使用remote进行通信。实现的功能都是一样的。从这个例子中可以发现:

  1. 使用 ipcMain/ipcRenderer 通信,业务逻辑同时存在于主进程和渲染进程的代码中。同时为了通信,会产生非常多的 event & event handler
  2. 使用remote通信,渲染进程直接获取主进程模块。而且,使用remote通信不需要使用事件和回调函数,写出来的代码清晰直观。

主进程可以视作为模块提供者,而渲染进程是模块的消费者,渲染进程通过remote来获取主进程的模块,实现业务逻辑。这样做有以下好处:

  1. 主进程/渲染进程代码解耦,职责分明,提升可维护性
  2. 业务逻辑内聚在渲染进程
  3. 减少主进程/渲染进程冗余无用的代码

具体实现

介绍了一下前置知识,现在来看看不同情况下,Electron进程通信的实现方法。

主进程和渲染进程通信

主进程发消息、渲染进程收消息:主进程使用窗口的 webContents 发消息,渲染进程内使用 ipcRenderer 收消息

// main.js
const win = new BrowserWindow();
win.load('index.html');
win.webContents.send('hello', {a: 1});


// index.html 中的js
const { ipcRenderer } = require('electron');
ipcRenderer.on('hello', (e, data) => {
    console.log(data); // 打印出 {a: 1}
})

渲染进程发消息、主进程收消息: 渲染进程使用 ipcRenderer 发消息,主进程使用 ipcMain 收消息。

// main.js
const { ipcMain } = require('electron');
ipcMain.on('hello', (e, data) => {
    console.log(data); // 打印出 {a: 1}
});


// index.html 中的js
const { ipcRenderer } = require('electron');
ipcRenderer.send('hello', {a: 1});

一般遇到主进程和渲染进程通信的情况,大部分都是渲染进程来需要获取主进程的模块,此时推荐使用 remote 来做通信。

// main.js
// 主进程无需添加任何代码

// index.html 中的js,获取主进程模块
const { remote } = require('electron');
const {app, BrowserWindow, dialog, ...} = remote;

渲染进程与渲染进程通信

渲染进程之间也是会频繁通信的,具体场景举例:在设置窗口点击更换皮肤,需要通知所有窗口进行颜色、背景的更新。

最佳实践:渲染进程A通过 remote 模块,获取到需要目标窗口的 webContents 对象,然后通过 webContents 向目标窗口的发送消息。目标窗口使用 ipcRenderer 监听事件。

const { remote } = require('electron')
const allWindows = remote.BrowserWindow.getAllWindows();

// 窗口A中的逻辑

// 1、第一步,获取到目标窗口的webContents
// 可以根据id,title来找到目标窗口,也可以用其他办法
const targetId = 1;
const targetTitle = '目标窗口';
// let targetWindow = allWindows.find(w => w.id === targetId);
let targetWindow = allWindows.find(w => w.title === targetTitle);

// 2、第二步,使用目标窗口的webContents发送消息
targetWindow.webContents.send('theme-change', 'gray');


// 目标窗口内的逻辑,使用ipcRenderer监听事件
// 窗口收到theme-change事件,改变窗口颜色。不需要关注事件从哪里发出,只需要关注接收到该事件后做什么
ipcRenderer.on('theme-change', (e, theme) => {
    console.log(theme); // gray
});

还有一种传统的办法,不用 remote ,改用 ipcMain 做通信,但是会在主进程冗余很多事件代码。因此还是推荐使用 remote ,理由同上。小例子:

// mian.js 
// 用于事件转发,没有实际的逻辑
ipcMain.on('send-event-to-window', (e, id, eventName, ...args) => {
    BrowserWindow.getAllWindows()
    .find(w = > w.id === id)
    .webContents
    .send(eventName, ...args);
});

// 窗口A内部,向主进程发事件
const targetId = 1;
ipcRenderer.send('send-event-to-window', id, 'theme-change', 'gray');

// 目标窗口
ipcRenderer.on('theme-change', (e, theme) => {
    console.log(theme); // gray
});

webview与渲染进程通信

内嵌的web页面运行在客户端中,也可以获取本地化的能力。此时,webview就需要与渲染进程通信了。

在文章开头讲到了,为了应用的安全性webview是需要禁用node集成的,通过preload的方式,注入了一个 ipcRenderer 并挂载到window上。

webview发消息,渲染进程收消息:webview内部使用 ipcRenderer.sendToHost 来发消息。渲染进程获取到webview的dom元素,监听dom元素的 ipc-message 事件接收消息

// 渲染进程拿到webview的dom,接收事件
const webview = document.querySelector('webview')
webview.addEventListener('ipc-message', (event) => {
  console.log(event.channel); // hello
});

// webview页面内,假装点了一个按钮,发送事件
btn.onclick = () => {
    window.ElectronIpcRenderer.sendToHost('hello')
}

渲染进程发消息,webview收消息:渲染进程使用 webview.send 发消息。webview使用内置的 ipcRenderer 收消息。

// webview内部
window.ElectronIpcRenderer.on('event-from-renderer', (e, data) => {
    console.log(e, data); // {a: 1}
});

// 渲染进程内部
const webview = document.querySelector('webview')
webview.send('event-from-renderer', {a: 1})

总结

这三种通信方式是最基础的,在此之上进行排列组合也是很常见的,这个由开发者自行拓展即可。

VbaYBz2.jpg!web

举一个小例子:webview内部触发更换皮肤功能 -> 通知渲染进程同步更新皮肤 -> 渲染进程收到消息,向其他渲染进程通信 -> 同步更新皮肤完成。

最后

欢迎大家在评论区讨论,技术交流 & 内推 -> [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK