难点:Electron WebSocket 保活
在 macOS 上,Electron 应用切换到后台后 WebSocket 连接中断,主要是因为 Chromium 为了节能,会主动挂起后台的渲染进程,暂停其 JavaScript 执行。
要解决这个问题,核心思路是将 WebSocket 连接从易被挂起的渲染进程,转移到生存周期更稳定的主进程。同时,配合一些系统层面的配置,可以最大程度保证连接的稳定性。
以下是几种可行的方法,可以组合使用:
🧠 核心方案:在主进程中管理 WebSocket 连接
这是最根本、最推荐的解决方案。渲染进程(网页)的生命周期短暂,页面刷新或关闭都会导致其中的 WebSocket 连接断开。而主进程(main.js)是 Node.js 环境,独立于窗口运行,生命周期与应用本身一致,是管理长连接的理想场所。
- 在主进程中创建并管理 WebSocket:推荐使用 Node.js 的
ws库,它功能更强大,更易于控制。在主进程中维护一个 WebSocket 单例。 - 通过 IPC 与渲染进程通信:使用 Electron 的
ipcMain和ipcRenderer模块,并配合contextBridge在preload.js中安全地暴露 API。这样,渲染进程就可以通过 IPC 触发主进程的 WebSocket 操作(如发送消息),并监听来自主进程的消息推送。
🛠️ 辅助方案:配置与机制
除了架构调整,还可以配合以下方法,进一步确保连接稳定。
-
禁用渲染进程的后台节流 (
backgroundThrottling):在创建BrowserWindow时,将webPreferences中的backgroundThrottling设置为false。这可以指示 Chromium 不要主动限制该窗口后台时的性能,从而让其中的 JavaScript(如果仍有)能继续运行。// 在主进程中const mainWindow = new BrowserWindow({// ... 其他配置webPreferences: {// ... 其他偏好backgroundThrottling: false, // 关键配置}}); -
防止应用被系统挂起 (
powerSaveBlocker):macOS 在屏幕关闭或系统空闲时可能会暂停应用。可以使用 Electron 的powerSaveBlocker模块来阻止这一行为。// 在主进程中const { powerSaveBlocker } = require('electron');// 启动阻止器,防止应用被挂起const blockerId = powerSaveBlocker.start('prevent-app-suspension');// 在应用退出时记得停止阻止器app.on('will-quit', () => {if (powerSaveBlocker.isStarted(blockerId)) {powerSaveBlocker.stop(blockerId);}});prevent-app-suspension模式会阻止应用被暂停,同时允许屏幕关闭,非常适合后台下载或保持连接等场景。 -
实现心跳与自动重连机制:无论采用何种保活策略,网络波动或服务端重启都可能导致连接断开。因此,心跳与自动重连是必不可少的。
- 心跳 (Heartbeat):在主进程中,定时(如每30秒)通过 WebSocket 向服务端发送一个轻量级的 ping 消息,服务端回复 pong,以保持连接活跃。
- 自动重连 (Auto-Reconnect):在 WebSocket 的
onclose或onerror事件中,加入指数退避的重连逻辑,避免在服务端不可用时频繁重连造成资源浪费。
💎 总结与版本提示
总的来说,保证 Electron 在 macOS 后台 WebSocket 不中断的最佳实践是:
- 首要:将 WebSocket 连接放在主进程中管理。
- 其次:配置
backgroundThrottling: false。 - 再次:考虑使用
powerSaveBlocker.start('prevent-app-suspension')。 - 最后但同样重要:实现心跳与自动重连机制。
另外,在较新版本的 Electron 中,曾修复过一个允许 WebSocket 不被后台化的 flag。虽然这不是一个需要开发者手动配置的选项,但它说明 Electron 团队一直在关注并改进后台连接的问题。因此,将 Electron 升级到最新稳定版也是一个好习惯,可以确保你获得最新的修复和改进。