0%

Electron 开发指南——桌面应用开发完整教程

今天下了大雪,就像 Electron 能跨平台一样,雪也能覆盖各种地形…

介绍

  Electron 是一个使用 Javascript、Html 和 Css 构建跨平台桌面应用程序的框架。它将 Chromium 和 Node.JS 结合在一起,让你可以使用 Web 技术构建跨平台的桌面应用。本文将深入探讨 Electron 的核心概念、常见使用场景、进程间通信以及跨平台开发的最佳实践。

Electron 核心概念

主进程(Main Process)和渲染进程(Renderer Process)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// main.JS - 主进程 const { app, BrowserWindow, ipcMain, Menu } = require('electron');
const path = require('path');

// 保持对主窗口的全局引用,避免被垃圾回收 let mainWindow;

function createWindow() {
// 创建浏览器窗口 mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false, // 在 Electron 12+中默认为 true
preload: path.join(__dirname, 'preload.JS')
},
icon: path.join(__dirname, 'assets/icon.png'),
backgroundColor: '#f0f0f0'
});

// 加载应用的 Html 文件 mainWindow.loadFile('index.Html');

// 打开开发者工具 if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}

// 监听窗口关闭事件 mainWindow.on('closed', () => {
mainWindow = null;
});

// 设置窗口菜单 createMenu();
}

// 创建应用菜单 function createMenu() {
const isMac = process.platform === 'darwin';

const template = [
// { role: 'appMenu' }
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),

// 编辑菜单
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
}
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}

// 应用准备好后创建窗口 app.whenReady().then(() => {
createWindow();

// 在 macOS 上,单击 dock 图标且没有其他窗口打开时重新创建窗口 app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

// 当所有窗口都关闭时退出应用 app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

// IPC 通信示例 - 主进程接收渲染进程消息 ipcMain.on('message-from-renderer', (event, data) => {
console.log('Received from renderer:', data);

// 回复渲染进程 event.reply('reply-from-main', {
status: 'success',
data: 'Message received by main process'
});
});

// IPC 异步处理示例 ipcMain.handle('async-operation', async (event, params) => {
// 在主进程中执行异步操作 return new Promise((resolve) => {
setTimeout(() => {
resolve({
result: `Processed in main process: ${params}`,
timestamp: Date.now()
});
}, 1000);
});
});

渲染进程(Renderer Process)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE Html>
<Html>
<head>
<meta charset="UTF-8">
<title>Electron Application</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.button {
background: #007acc;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.button:hover {
background: #005a9e;
}
</style>
</head>
<body>
<div class="container">
<h1>Electron Desktop Application</h1>
<div id="status">Ready</div>

<button class="button" id="sendMessage">Send Message to Main Process</button>
<button class="button" id="asyncOperation">Perform Async Operation</button>
<button class="button" id="openFile">Open File Dialog</button>
<button class="button" id="showNotification">Show Notification</button>

<div id="result"></div>
</div>

<script src="renderer.JS"></script>
</body>
</Html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// renderer.JS - 渲染进程 const { ipcRenderer, shell, clipboard, nativeImage } = require('electron');

// DOM 元素 const statusDiv = document.getElementById('status');
const resultDiv = document.getElementById('result');
const sendMessageBtn = document.getElementById('sendMessage');
const asyncOpBtn = document.getElementById('asyncOperation');
const openFileBtn = document.getElementById('openFile');
const notificationBtn = document.getElementById('showNotification');

// IPC 通信示例 sendMessageBtn.addEventListener('click', () => {
statusDiv.textContent = 'Sending message to main process...';

// 发送消息到主进程 ipcRenderer.send('message-from-renderer', {
type: 'test-message',
timestamp: Date.now(),
content: 'Hello from renderer process!'
});
});

// 监听主进程回复 ipcRenderer.on('reply-from-main', (event, data) => {
statusDiv.textContent = 'Message received from main process';
resultDiv.innerHTML = `<pre>${Json.stringify(data, null, 2)}</pre>`;
});

// 异步操作示例 asyncOpBtn.addEventListener('click', async () => {
try {
statusDiv.textContent = 'Performing async operation...';

// 调用主进程的异步操作 const result = await ipcRenderer.invoke('async-operation', 'test parameter');

resultDiv.innerHTML = `<pre>${Json.stringify(result, null, 2)}</pre>`;
statusDiv.textContent = 'Async operation completed';
} catch (error) {
console.error('Async operation failed:', error);
statusDiv.textContent = 'Async operation failed';
}
});

// 文件对话框示例 openFileBtn.addEventListener('click', async () => {
try {
const { dialog } = require('electron').remote || require('@electron/remote');

const result = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'],
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'All Files', extensions: ['*'] }
]
});

if (!result.canceled) {
console.log('Selected files:', result.filePaths);
resultDiv.innerHTML = `<p>Selected files: ${result.filePaths.join(', ')}</p>`;
}
} catch (error) {
console.error('File dialog error:', error);
}
});

// 通知示例 notificationBtn.addEventListener('click', () => {
if (Notification.permission === 'granted') {
new Notification('Electron Notification', {
body: 'This is a desktop notification from Electron!',
icon: 'path/to/icon.png' // 如果有的话
});
} else {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
new Notification('Electron Notification', {
body: 'This is a desktop notification from Electron!'
});
}
});
}
});

// 监听 IPC 消息 ipcRenderer.on('system-info', (event, info) => {
resultDiv.innerHTML = `<p>System Info: ${Json.stringify(info)}</p>`;
});

预加载脚本(Preload Script)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// preload.JS - 预加载脚本 const { contextBridge, ipcRenderer } = require('electron');

// 安全地暴露 API 到渲染进程 contextBridge.exposeInMainWorld('electronAPI', {
// IPC 通信 API
sendMessage: (message) => ipcRenderer.invoke('send-message', message),
onMessage: (callback) => ipcRenderer.on('receive-message', callback),
removeMessageListener: (callback) => ipcRenderer.removeListener('receive-message', callback),

// 系统信息 API
getSystemInfo: () => ipcRenderer.invoke('get-system-info'),

// 文件操作 API
openFileDialog: () => ipcRenderer.invoke('open-file-dialog'),
saveFileDialog: (data) => ipcRenderer.invoke('save-file-dialog', data),

// 窗口控制 API
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window'),

// Shell 操作 API
openExternal: (url) => shell.openExternal(url),

// 状态信息 getAppInfo: () => ipcRenderer.invoke('get-app-info')
});

// 渲染进程使用示例 window.addEventListener('DOMContentLoaded', () => {
// 使用暴露的 API
document.getElementById('btn').addEventListener('click', async () => {
const systemInfo = await window.electronAPI.getSystemInfo();
console.log('System Info:', systemInfo);
});
});

进程间通信(IPC)

基本 IPC 通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// main.JS - 主进程中的 IPC 处理 const { ipcMain, dialog, app, BrowserWindow } = require('electron');

// 同步消息处理(不推荐,可能阻塞)
ipcMain.on('sync-request', (event, arg) => {
// 同步操作 const result = performSyncOperation(arg);
event.returnValue = result;
});

// 异步消息处理(推荐)
ipcMain.handle('async-request', async (event, data) => {
try {
// 执行异步操作 const result = await performAsyncOperation(data);
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
});

// 发送消息到渲染进程 function broadcastToAllWindows(channel, data) {
BrowserWindow.getAllWindows().forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send(channel, data);
}
});
}

// 向特定窗口发送消息 function sendToWindow(window, channel, data) {
if (window && !window.isDestroyed()) {
window.webContents.send(channel, data);
}
}

// 渲染进程接收主进程消息
// renderer.JS
ipcRenderer.on('broadcast-message', (event, data) => {
console.log('Received broadcast:', data);
updateUI(data);
});

// 发送消息到主进程并等待回复 async function sendMessageToMain(message) {
try {
const response = await ipcRenderer.invoke('async-request', message);
return response;
} catch (error) {
console.error('IPC call failed:', error);
return { success: false, error: error.message };
}
}

复杂 IPC 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// ipcManager.JS - IPC 管理器 class IPCManager {
constructor(mainWindow) {
this.mainWindow = mainWindow;
this.channels = new Map();
this.setupChannels();
}

setupChannels() {
// 文件操作通道 ipcMain.handle('file:read', async (event, filePath) => {
try {
const fs = require('fs').promises;
const data = await fs.readFile(filePath, 'utf8');
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
});

ipcMain.handle('file:write', async (event, filePath, content) => {
try {
const fs = require('fs').promises;
await fs.writeFile(filePath, content, 'utf8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});

// 系统信息通道 ipcMain.handle('system:info', async (event) => {
const os = require('os');
const fs = require('fs').promises;

return {
platform: os.platform(),
arch: os.arch(),
release: os.release(),
uptime: os.uptime(),
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
cpuCount: os.cpus().length,
loadAverage: os.loadavg()
};
});

// 数据库操作通道 ipcMain.handle('db:query', async (event, query, params) => {
// 这里可以集成数据库操作 return this.executeQuery(query, params);
});
}

// 注册自定义通道 registerChannel(channel, handler) {
if (this.channels.has(channel)) {
console.warn(`Channel ${channel} already registered`);
}

this.channels.set(channel, handler);
ipcMain.handle(channel, handler);
}

// 注销通道 unregisterChannel(channel) {
if (this.channels.has(channel)) {
ipcMain.removeHandler(channel);
this.channels.delete(channel);
}
}

// 广播消息 broadcast(channel, data) {
BrowserWindow.getAllWindows().forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send(channel, data);
}
});
}

// 发送给特定窗口 sendToWindow(window, channel, data) {
if (window && !window.isDestroyed()) {
window.webContents.send(channel, data);
}
}
}

// 使用示例 const ipcManager = new IPCManager(mainWindow);

// 注册自定义处理函数 ipcManager.registerChannel('custom:operation', async (event, data) => {
// 执行自定义操作 return { result: 'Custom operation completed', data };
});

跨平台功能实现

Windows 特定功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// windowsSpecific.JS - Windows 特定功能 const { app, nativeImage } = require('electron');
const path = require('path');

class WindowsFeatures {
static isWindows() {
return process.platform === 'win32';
}

// 设置 Windows 任务栏图标 static setupTaskbarIcon(browserWindow, iconPath) {
if (this.isWindows()) {
browserWindow.setIcon(iconPath);
}
}

// Windows 文件关联 static handleFileAssociations() {
if (this.isWindows()) {
// 处理命令行参数中的文件路径 const argv = process.argv.slice(1);

if (argv.length > 0) {
argv.forEach(arg => {
if (arg.endsWith('.myext')) { // 自定义文件扩展名
// 处理文件 this.openFile(arg);
}
});
}
}
}

// Windows 通知中心集成 static showWindowsNotification(title, body, options = {}) {
if (this.isWindows()) {
const { Notification } = require('electron');

const notification = new Notification({
title,
body,
...options
});

notification.show();
return notification;
}
}

// Windows 系统托盘 static createTrayIcon(trayImagePath) {
if (this.isWindows()) {
const { Tray, Menu } = require('electron');

const tray = new Tray(trayImagePath);
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show', click: () => this.showMainWindow() },
{ label: 'Settings', click: () => this.openSettings() },
{ label: 'Quit', click: () => app.quit() }
]);

tray.setContextMenu(contextMenu);
tray.setToolTip('My Electron App');

return tray;
}
}

// 获取 Windows 特定路径 static getWindowsPaths() {
if (this.isWindows()) {
const { app } = require('electron');

return {
appData: app.getPath('appData'),
userData: app.getPath('userData'),
temp: app.getPath('temp'),
home: app.getPath('home'),
desktop: app.getPath('desktop')
};
}
return {};
}
}

module.exports = WindowsFeatures;

macOS 特定功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// macOSSpecific.JS - macOS 特定功能 const { app, Menu, nativeImage } = require('electron');

class MacFeatures {
static isMac() {
return process.platform === 'darwin';
}

// 设置 macOS 菜单 static setupMacMenu() {
if (this.isMac()) {
const template = [
{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
]
}
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
}

// Touch Bar 支持 static setupTouchBar(mainWindow) {
if (this.isMac()) {
const { TouchBar } = require('electron');
const { TouchBarButton, TouchBarLabel, TouchBarSpacer } = TouchBar;

const button = new TouchBarButton({
label: 'Click Me',
backgroundColor: '#787878',
click: () => {
console.log('Button clicked');
}
});

const label = new TouchBarLabel({
label: 'Current State',
textColor: '#FFFFFF'
});

const spacer = new TouchBarSpacer({ size: 'large' });

const touchBar = new TouchBar({
items: [button, spacer, label]
});

mainWindow.setTouchBar(touchBar);
}
}

// Dock 菜单 static setupDockMenu() {
if (this.isMac()) {
const dockMenu = Menu.buildFromTemplate([
{ label: 'New Window', click: () => this.createNewWindow() },
{ label: 'Settings', click: () => this.openSettings() },
{ label: 'About', click: () => this.showAbout() }
]);

app.dock.setMenu(dockMenu);
}
}

// 文件关联处理 static handleOpenFileEvent() {
if (this.isMac()) {
app.on('open-file', (event, filePath) => {
event.preventDefault();
this.openFile(filePath);
});
}
}

// 全屏优化 static optimizeForFullscreen(browserWindow) {
if (this.isMac()) {
browserWindow.setFullScreenable(true);
browserWindow.setAutoHideMenuBar(true);
browserWindow.setMenuBarVisibility(false);
}
}
}

module.exports = MacFeatures;

Linux 特定功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// linuxSpecific.JS - Linux 特定功能 const { app } = require('electron');

class LinuxFeatures {
static isLinux() {
return process.platform === 'linux';
}

// 设置 Linux 图标 static setupLinuxIcons(browserWindow, iconPath) {
if (this.isLinux()) {
browserWindow.setIcon(iconPath);
}
}

// Unity 启动器集成 static setupUnityLauncher() {
if (this.isLinux()) {
// Unity/Dock 集成 app.setBadgeCount(0); // 设置应用徽章计数

// 任务进度
// browserWindow.setProgressBar(progress); // 0.0 - 1.0
}
}

// 通知系统集成 static showLinuxNotification(title, body, options = {}) {
if (this.isLinux()) {
const { Notification } = require('electron');

const notification = new Notification({
title,
body,
...options
});

notification.show();
return notification;
}
}

// 获取 Linux 特定路径 static getLinuxPaths() {
if (this.isLinux()) {
return {
config: app.getPath('config'),
cache: app.getPath('cache'),
home: app.getPath('home'),
downloads: app.getPath('downloads')
};
}
return {};
}
}

module.exports = LinuxFeatures;

实际应用案例

文件管理器应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// fileManager.JS - 文件管理器示例 const { ipcMain, dialog, shell } = require('electron');
const fs = require('fs').promises;
const path = require('path');

class FileManager {
constructor() {
this.setupIPC();
}

setupIPC() {
// 读取目录内容 ipcMain.handle('fm:list-directory', async (event, dirPath) => {
try {
const items = await fs.readdir(dirPath, { withFileTypes: true });
const fileInfos = await Promise.all(items.map(async (item) => {
const fullPath = path.join(dirPath, item.name);
const stat = await fs.stat(fullPath);

return {
name: item.name,
path: fullPath,
isDirectory: item.isDirectory(),
isFile: item.isFile(),
size: stat.size,
modified: stat.mtime,
created: stat.birthtime
};
}));

return { success: true, items: fileInfos };
} catch (error) {
return { success: false, error: error.message };
}
});

// 创建目录 ipcMain.handle('fm:create-directory', async (event, dirPath, dirName) => {
try {
const newDirPath = path.join(dirPath, dirName);
await fs.mkdir(newDirPath);
return { success: true, path: newDirPath };
} catch (error) {
return { success: false, error: error.message };
}
});

// 重命名文件/目录 ipcMain.handle('fm:rename-item', async (event, oldPath, newPath) => {
try {
await fs.rename(oldPath, newPath);
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});

// 删除文件/目录 ipcMain.handle('fm:delete-items', async (event, paths) => {
try {
for (const itemPath of paths) {
const stat = await fs.stat(itemPath);
if (stat.isDirectory()) {
await this.deleteDirectoryRecursive(itemPath);
} else {
await fs.unlink(itemPath);
}
}
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});

// 打开文件位置 ipcMain.handle('fm:reveal-in-finder', async (event, filePath) => {
try {
shell.showItemInFolder(filePath);
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
}

// 递归删除目录 async deleteDirectoryRecursive(dirPath) {
const items = await fs.readdir(dirPath);

for (const item of items) {
const itemPath = path.join(dirPath, item);
const stat = await fs.stat(itemPath);

if (stat.isDirectory()) {
await this.deleteDirectoryRecursive(itemPath);
} else {
await fs.unlink(itemPath);
}
}

await fs.rmdir(dirPath);
}
}

// 初始化文件管理器 new FileManager();

媒体播放器应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// mediaPlayer.JS - 媒体播放器示例 const { ipcMain } = require('electron');
const path = require('path');
const fs = require('fs').promises;

class MediaPlayer {
constructor() {
this.playbackState = {
isPlaying: false,
currentTime: 0,
duration: 0,
volume: 1.0,
playlist: [],
currentIndex: 0
};

this.setupIPC();
}

setupIPC() {
// 加载播放列表 ipcMain.handle('player:load-playlist', async (event, playlistPath) => {
try {
const playlist = await this.scanMediaFiles(playlistPath);
this.playbackState.playlist = playlist;
this.playbackState.currentIndex = 0;
return { success: true, playlist };
} catch (error) {
return { success: false, error: error.message };
}
});

// 播放控制 ipcMain.handle('player:play', async (event) => {
this.playbackState.isPlaying = true;
return { success: true, state: this.playbackState };
});

ipcMain.handle('player:pause', async (event) => {
this.playbackState.isPlaying = false;
return { success: true, state: this.playbackState };
});

ipcMain.handle('player:stop', async (event) => {
this.playbackState.isPlaying = false;
this.playbackState.currentTime = 0;
return { success: true, state: this.playbackState };
});

// 音量控制 ipcMain.handle('player:set-volume', async (event, volume) => {
this.playbackState.volume = Math.max(0, Math.min(1, volume));
return { success: true, volume: this.playbackState.volume };
});

// 播放进度 ipcMain.handle('player:seek', async (event, time) => {
this.playbackState.currentTime = time;
return { success: true, currentTime: time };
});

// 下一首/上一首 ipcMain.handle('player:next', async (event) => {
if (this.playbackState.currentIndex < this.playbackState.playlist.length - 1) {
this.playbackState.currentIndex++;
return { success: true, currentIndex: this.playbackState.currentIndex };
}
return { success: false, error: 'No more tracks' };
});

ipcMain.handle('player:previous', async (event) => {
if (this.playbackState.currentIndex > 0) {
this.playbackState.currentIndex--;
return { success: true, currentIndex: this.playbackState.currentIndex };
}
return { success: false, error: 'Already at first track' };
});
}

// 扫描媒体文件 async scanMediaFiles(directory) {
const mediaExtensions = ['.mp3', '.mp4', '.avi', '.mov', '.mkv', '.flv', '.wav', '.m4a'];
const files = [];

try {
const items = await fs.readdir(directory);

for (const item of items) {
const fullPath = path.join(directory, item);
const stat = await fs.stat(fullPath);

if (stat.isDirectory()) {
// 递归扫描子目录 const subFiles = await this.scanMediaFiles(fullPath);
files.push(...subFiles);
} else {
const ext = path.extname(item).toLowerCase();
if (mediaExtensions.includes(ext)) {
files.push({
name: item,
path: fullPath,
type: ext.substring(1), // 移除点号 size: stat.size
});
}
}
}
} catch (error) {
console.error('Error scanning directory:', error);
}

return files;
}
}

// 初始化媒体播放器 new MediaPlayer();

性能优化和安全

性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// performanceOptimizer.JS - 性能优化 const { app, ipcMain, powerSaveBlocker } = require('electron');

class PerformanceOptimizer {
constructor() {
this.resourceMonitor = null;
this.setupPerformanceOptimizations();
}

setupPerformanceOptimizations() {
// 内存监控 setInterval(() => {
const memoryUsage = process.memoryUsage();
if (memoryUsage.heapUsed > 200 * 1024 * 1024) { // 200MB
console.warn('High memory usage detected:', memoryUsage);
this.cleanupResources();
}
}, 5000);

// GPU 优化 app.commandLine.appendSwitch('enable-accelerated-2d-canvas');
app.commandLine.appendSwitch('enable-webgl');
app.commandLine.appendSwitch('ignore-gpu-blacklist');

// 预加载优化 app.commandLine.appendSwitch('JS-flags', '--max-old-space-size=4096');
}

// 资源清理 cleanupResources() {
// 清理缓存 if (this.resourceMonitor) {
this.resourceMonitor.clearCache();
}

// 强制垃圾回收(如果可用)
if (global.gc) {
global.gc();
}
}

// 防休眠 preventSleep() {
const id = powerSaveBlocker.start('prevent-display-sleep');
console.log('Power save blocker started:', id);
return id;
}

// 允许休眠 allowSleep(id) {
if (powerSaveBlocker.isStarted(id)) {
powerSaveBlocker.stop(id);
console.log('Power save blocker stopped:', id);
}
}

// 渲染进程优化 optimizeRenderer(renderer) {
// 禁用不必要的功能 renderer.webContents.setAudioMuted(true);
renderer.webContents.setBackgroundThrottling(false);

// 性能偏好设置 renderer.setBackgroundColor('#ffffff');
}
}

// 内存监控 IPC
ipcMain.handle('perf:get-memory-info', async () => {
const memoryUsage = process.memoryUsage();
const heapStats = v8.getHeapStatistics();

return {
memoryUsage,
heapStats,
uptime: process.uptime()
};
});

new PerformanceOptimizer();

安全最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// securityManager.JS - 安全管理 const { app, session, ipcMain, dialog } = require('electron');
const path = require('path');

class SecurityManager {
constructor() {
this.setupSecurityPolicies();
}

setupSecurityPolicies() {
// 设置安全策略 app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);

// 限制导航到外部站点 if (parsedUrl.origin !== 'file://' && !parsedUrl.origin.startsWith('devtools://')) {
event.preventDefault();
}
});

contents.setWindowOpenHandler(({ url }) => {
// 安全地打开外部链接 if (url.startsWith('https://')) {
require('electron').shell.openExternal(url);
}
return { action: 'deny' };
});
});

// 内容安全策略 app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
// 对于开发环境可以接受自签名证书 if (process.env.NODE_ENV === 'development') {
callback(true);
} else {
callback(false);
}
});

// CSP 设置 session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https:"
].join('; ')
}
});
});
}

// 安全的 IPC 处理 setupSecureIPC() {
// 验证 IPC 消息参数 ipcMain.handle('secure-operation', async (event, data) => {
// 验证输入 if (!this.isValidInput(data)) {
throw new Error('Invalid input data');
}

// 执行安全操作 return await this.performSecureOperation(data);
});
}

// 输入验证 isValidInput(data) {
if (typeof data !== 'object' || data === null) {
return false;
}

// 验证字符串长度 if (typeof data.input === 'string' && data.input.length > 1000) {
return false;
}

// 验证路径安全性 if (typeof data.filePath === 'string') {
const normalizedPath = path.normalize(data.filePath);
const allowedRoot = app.getPath('userData');

if (!normalizedPath.startsWith(allowedRoot)) {
return false;
}
}

return true;
}

async performSecureOperation(data) {
// 执行安全操作
// 这里可以包含验证、授权检查等 return { success: true, result: 'Operation completed securely' };
}

// 文件系统安全 sanitizeFilePath(inputPath) {
// 移除危险字符 const sanitized = inputPath.replace(/\.\.(\/|\\)/g, '');

// 确保路径在允许的范围内 const absolutePath = path.resolve(sanitized);
const userDataPath = app.getPath('userData');

if (!absolutePath.startsWith(userDataPath)) {
throw new Error('Path traversal detected');
}

return absolutePath;
}
}

new SecurityManager();

打包和分发

打包配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// package.Json - Electron 应用配置
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "A sample Electron application",
"main": "main.JS",
"scripts": {
"start": "electron .",
"start:dev": "NODE_ENV=development electron .",
"build": "electron-builder",
"dist": "electron-builder --publish=never",
"dist:win": "electron-builder --win",
"dist:mac": "electron-builder --mac",
"dist:linux": "electron-builder --linux"
},
"build": {
"appId": "com.example.myapp",
"productName": "My Electron App",
"directories": {
"output": "dist"
},
"files": [
"main.JS",
"preload.JS",
"renderer.JS",
"index.Html",
"assets/**/*",
"node_modules/**/*"
],
"asar": true,
"mac": {
"category": "public.app-category.productivity",
"icon": "assets/icon.icns",
"target": [
"dmg",
"zip"
]
},
"win": {
"icon": "assets/icon.ico",
"target": [
"nsis",
"portable"
]
},
"linux": {
"icon": "assets/icon.png",
"target": [
"AppImage",
"deb",
"rpm"
],
"category": "Utility"
}
},
"devDependencies": {
"electron": "^latest",
"electron-builder": "^latest"
}
}

自动更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// updater.JS - 自动更新 const { app, ipcMain, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');

class AppUpdater {
constructor() {
this.initializeAutoUpdater();
}

initializeAutoUpdater() {
// 配置更新服务器 autoUpdater.setFeedURL({
provider: 'github',
owner: 'your-username',
repo: 'your-repo'
});

// 检查更新 autoUpdater.on('checking-for-update', () => {
console.log('Checking for update...');
});

autoUpdater.on('update-available', (info) => {
console.log('Update available:', info);
dialog.showMessageBox({
type: 'info',
title: 'Update Available',
message: `A new version (${info.version}) is available. Downloading...`,
buttons: ['OK']
});
});

autoUpdater.on('update-not-available', (info) => {
console.log('Update not available');
});

autoUpdater.on('error', (err) => {
console.error('Auto-updater error:', err);
});

autoUpdater.on('download-progress', (progress) => {
console.log(`Download progress: ${progress.percent}%`);
});

autoUpdater.on('update-downloaded', (info) => {
dialog.showMessageBox({
type: 'info',
title: 'Update Ready',
message: 'Update downloaded. Restart to install?',
buttons: ['Restart', 'Later']
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});
}

// 手动检查更新 checkForUpdates() {
autoUpdater.checkForUpdates();
}

// 获取更新信息 getUpdateInfo() {
return autoUpdater.currentVersion;
}
}

// IPC 处理自动更新 ipcMain.handle('app:check-update', async () => {
return new Promise((resolve) => {
const checkTimer = setTimeout(() => {
resolve({ status: 'timeout' });
}, 10000);

autoUpdater.once('update-available', (info) => {
clearTimeout(checkTimer);
resolve({ status: 'available', version: info.version });
});

autoUpdater.once('update-not-available', () => {
clearTimeout(checkTimer);
resolve({ status: 'not-available' });
});

autoUpdater.checkForUpdates();
});
});

new AppUpdater();

总结

  • Electron 通过结合 Web 技术和原生系统 API,实现了真正的跨平台桌面应用开发
  • 主进程和渲染进程的架构提供了良好的隔离性和安全性
  • IPC 通信是实现进程间协作的关键机制
  • 跨平台开发需要针对不同操作系统进行专门的优化
  • 性能优化和安全防护是生产环境应用的必备要素
  • 合理的打包策略和自动更新机制能提升用户体验
  • 虽然包体积较大,但 Electron 仍是快速开发跨平台桌面应用的有效选择

今天外面下了大雪,就像 Electron 的跨平台能力一样,无论在 Windows、macOS 还是 Linux 上,都能覆盖各种”地形”,提供一致的用户体验。Electron 就像雪花一样,看似简单,但组合起来却能创造出美丽的世界。

最佳实践

实践推荐做法
架构设计合理分离主进程和渲染进程职责
IPC 通信优先使用ipcRenderer.invoke进行异步通信
安全性启用 contextIsolation,使用预加载脚本
性能避免在渲染进程中执行 CPU 密集型任务
包体积使用 ASAR 打包,按需加载
用户体验提供加载状态和错误处理

扩展阅读

  1. Electron 官方文档
  2. Electron 安全指南
  3. Electron 最佳实践
  4. Electron Builder 文档

练习建议

  1. 创建一个简单的笔记应用
  2. 实现跨进程通信机制
  3. 集成系统托盘功能
  4. 实现自动更新功能
  5. 研究性能优化技巧
bulb