Electron在鸿蒙PC上搞多窗口通信,我被状态同步坑了半宿
上周接了个需求,要在鸿蒙PC版的Electron应用里做分屏编辑——左边是项目列表,右边双击打开详情页,最多同时开四个窗口。听起来不复杂,对吧?我在Windows上半小时就写了个demo,信心满满地丢到鸿蒙PC上跑,结果一晚上过去,窗口间传个数据跟打地鼠似的,这个收了那个丢,关了窗口内存还不释放。下面记录一下我踩过的四个坑,以及最后跑通的方案。
坑一:窗口ID在鸿蒙上跳号,我按ID存状态全乱了
Electron的BrowserWindow有个id属性,官方文档说它是递增的整数。我在Windows上惯用的套路是:
const win = new BrowserWindow({ width: 1200, height: 800 });windowMap.set(win.id, win); // key就是1,2,3,4...
这样后续按ID发消息、关窗口清理,都很省事。可同样的代码放到鸿蒙PC上,win.id直接给我来了个3、7、12,中间空了一大截。我一开始以为是代码哪里多实例化了,加了日志一看,每new BrowserWindow()一次,id确实在涨,但就是不连续。
后来翻Chromium源码才明白,鸿蒙PC上的窗口管理走了UIExtensionAbility的框架,Electron底层每创建一个窗口,实际上要和系统的WindowManager注册一次,中间有些内部窗口也占ID。这就导致BrowserWindow的id不是严格递增的。
我推荐的做法是放弃依赖win.id,自己维护一套映射:
// main/window-manager.jsconst { BrowserWindow, ipcMain } = require('electron');let internalIdCounter = 1;const windowRegistry = new Map(); // internalId -> { win, meta }functioncreateEditorWindow(projectId) { const internalId = internalIdCounter++; const win = new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: require('path').join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false } }); windowRegistry.set(internalId, { win, meta: { projectId, createdAt: Date.now() } }); win.loadURL(`http://localhost:3000/editor?id={internalId} leaked, forcing cleanup`); doCleanup(); } }, 5000);}
三重监听 + 5秒强制清理,这套组合拳上去之后,内存终于稳住了。关窗口后内存能回落到基线附近,再也没有泄露。
一套跑通的完整代码
把上面这些碎片拼起来,就是我目前在鸿蒙PC上稳定运行的多窗口管理模块。核心文件就三个:window-manager.js负责创建和注册窗口,safe-ipc.js处理可靠广播,preload.js暴露给渲染进程用的API。
我个人比较满意的是internalId那套设计,它解耦了Electron内部ID和的业务逻辑,以后就算Electron在鸿蒙上的行为再变,业务代码也不用改。另外sendToWindow里的executeJavaScript('1')虽然看起来像个黑魔法,但确实救了我一命——如果你也遇到后台窗口收不到IPC的问题,不妨先试试这招,比升级Electron版本成本低多了。
不过要提醒的是,这套方案目前只在Electron 28 + 鸿蒙PC 4.2上验证过。Electron 30之后Chromium内核升级了不少,IPC的实现可能也有变化,建议升级前先在测试环境跑一遍全套窗口压力测试。
本文遵循 MIT 协议转载授权。
如需转载,请注明原文出处及作者信息。商业用途或大规模分发前,建议联系作者获取书面许可。代码示例可直接复制使用,但作者不对因使用本文代码导致的任何损失承担责任。