Appearance
cx
Appearance
假设我们的电脑看作是一个工厂,电脑上面是可以运行各种应用程序的(浏览器、Word、音乐播放器、视频播放器....)每一个应用程序都可以看作是一个独立的工作区域,这个独立的工作区域就是我们的进程。
每个进程都会有独立的内存空间和系统资源,每个进程之间是独立的,这意味着假设有一个进程崩了,那么不会影响其他的进程。
刚才我们将进程比做工厂里面一个独立的工作区域,那么每个工作区域都有员工的,一个独立的工作区域是可以有多个员工的,类似的,一个进程也可以有多个线程,线程之间进行协同工作,共享相同的数据和资源。线程是操作系统所能够调度的最小单位。

同样都是线程,其中的一个线程能够创建其他的 6 个线程,并且有决定这些线程能够做什么的能力,那么这个线程就被称之为主线程。
在一个进程中所拥有的所有的资源,所有的线程都有权利去使用,这个就叫做“进程资源共享”。
理论上来讲,一个应用会对应一个进程,但是这并不是绝对的。一些大型的应用,在进行架构设计的时候,会设计为多进程应用。比较典型的就是 Chrome 浏览器。在 Chrome 浏览器中,一个标签页会对应一个进程,当前还有很多除了标签页以外的一些其他的进程。这样做的好处在于一个标签页崩溃后,不会影响其他的标签页。

和前面所提到的主线程类似,如果一个应用是多进程应用,那么也会有一个“主进程”,起到一个协调和管理其他子进程的作用。
例如,在 Node.js 里面,我们可以通过 child_process 这个模块来创建一个子进程,那么在这种情况下,启动这些子进程的 Node.js 应用实例就会被看作是主进程,child_process 就是子进程。主进程负责管理这些子进程,比如分配任务,处理通信和同步数据之类的。
Electron 是一个多进程的模型,每个Electron 应用都有一个单一的主进程,作为应用程序的入口点。主程序在NOde.js环境中运行,这意味着它具有require模块和使用所有Node.js API的能力

这里面 Electron 是主进程,对应的就是我们应用入口文件的 index.js,该主进程负责的任务有:
Electron Helper(Renderer)该进程就是我们窗口所对应的渲染进程。
假设在任务管理器将该进程关闭掉,我们会发现窗口不再渲染任何的东西,但是应用还存在,窗口也还存在。
这里就需要说一下,实际上在 Electron 应用中,有一个窗口进程,由窗口进程来创建的窗口,之后才是渲染进程来渲染的页面。这也是为什么我们关闭了渲染进程,但是窗口还存在的原因。

假设我们创建了多个窗口,那么会有多个窗口进程么?
多个窗口下仍然只有一个窗口进程,由这个窗口进程负责绘制多个窗口,不同的窗口里面会有不同的渲染进程来渲染页面。
如下图所示:

最后再明确一个点,一个窗口只能对应一个渲染进程么 ?
其实也不是,哪怕是在一个窗口里面,也是可以有多个渲染进程的。如何做到?通过 webview 加载其他的页面,当使用 webview 的时候,也会对应一个渲染进程。
上下文隔离功能将确保您的 预加载脚本 和 Electron的内部逻辑运行在所加载的 webcontent 网页 之外的另一个独立的上下文环境里。 这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和您的预加载脚本可访问的高等级权限的API 。
这意味着,实际上,您的预加载脚本访问的 window 对象并不是网站所能访问的对象。 例如,如果您在预加载脚本中设置 window.hello = 'wave' 并且启用了上下文隔离,当网站尝试访问window.hello对象时将返回 undefined。
自 Electron 12 以来,默认情况下已启用上下文隔离,并且它是 _所有应用程序_推荐的安全设置。
// 上下文隔离禁用的情况下使用预加载
window.myAPI = {
doAThing: () => {}
}// 在渲染器进程使用导出的 API
window.myAPI.doAThing()// 在上下文隔离启用的情况下使用预加载
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})// 在渲染器进程使用导出的 API
window.myAPI.doAThing()contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})export interface IElectronAPI {
loadPreferences: () => Promise<void>,
}
declare global {
interface Window {
electronAPI: IElectronAPI
}
}window.electronAPI.loadPreferences()const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
Title: <input id="title"/>
<button id="btn" type="button">Set</button>
<script src="./renderer.js"></script>
</body>
</html>const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI.setTitle(title)
})流程解析
renderer.js监听btn 按钮被点击 ➡️ window.electronAPI.setTitle ➡️ preload.js被调用触发ipcRenderer.send ➡️ main.js 监听到set-title触发 ipcMain.on('set-title')
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main')
const path = require('node:path')
async function handleFileOpen () {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (!canceled) {
return filePaths[0]
}
}
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('dialog:openFile', handleFileOpen)
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Dialog</title>
</head>
<body>
<button type="button" id="btn">Open a File</button>
File path: <strong id="filePath"></strong>
<script src='./renderer.js'></script>
</body>
</html>const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement.innerText = filePath
})流程解析
renderer.js 监听btn 按钮被点击 ➡️ window.electronAPI.openFile ➡️ preload.js被调用触发ipcRenderer.invoke ➡️ main.js 监听到dialog:openFile触发 ipcMain.handle('dialog:openFile') ➡️ 选择文件,将选择文件结构用promise返回到 renderer.js
或者是使用ipcRenderer.sendSync 方式,但是这种返回的是同步的,它将阻塞渲染器进程,直到收到回复为止。
const { app, BrowserWindow, Menu, ipcMain } = require('electron/main')
const path = require('node:path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
const menu = Menu.buildFromTemplate([
{
label: app.name,
submenu: [
{
click: () => mainWindow.webContents.send('update-counter', 1),
label: 'Increment'
},
{
click: () => mainWindow.webContents.send('update-counter', -1),
label: 'Decrement'
}
]
}
])
Menu.setApplicationMenu(menu)
mainWindow.loadFile('index.html')
// Open the DevTools.
mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
ipcMain.on('counter-value', (_event, value) => {
console.log(value) // will print value to Node console
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
counterValue: (value) => ipcRenderer.send('counter-value', value)
})<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Menu Counter</title>
</head>
<body>
Current value: <strong id="counter">0</strong>
<script src="./renderer.js"></script>
</body>
</html>const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue.toString()
window.electronAPI.counterValue(newValue)
})流程解析