Electron中Windows平台自定义协议替代app.on('open-url')的实现方案
解决Electron跨平台自定义协议处理问题(macOS + Windows)
我太懂你现在的纠结了——macOS上自定义协议跑起来丝滑得很,到Windows直接罢工,旧的makeSingleInstance还被废弃了,官方推的requestSingleInstanceLock又好像拿不到URL。别慌,我做Electron应用时踩过一模一样的坑,现在把完整的跨平台解决方案分享给你。
核心思路
Electron在Windows和macOS上处理自定义协议的机制天生不一样,得针对性适配:
- macOS:不管是首次启动还是后续用协议唤起,都能通过
open-url事件拿到URL - Windows:
- 首次通过协议启动时,URL会作为命令行参数传入
process.argv - 后续通过协议唤起时,因为
requestSingleInstanceLock锁定了单实例,新实例会触发已有实例的second-instance事件,URL就藏在这个事件的参数里
- 首次通过协议启动时,URL会作为命令行参数传入
我们要做的就是把这几种情况收拢到同一个逻辑里处理,实现跨平台一致性。
修改后的完整代码
1. 主进程index.js
const { app } = require('electron'); const createWindow = require('./utils/createWindow'); const handleOpenURL = require('./handleOpenURL'); const PROTOCOL = 'your-custom-protocol'; // 替换成你的自定义协议名 // 解析Windows首次启动时命令行里的协议URL function parseWinLaunchArgs() { if (process.platform === 'win32') { // 从命令行参数里过滤出协议开头的内容 const targetURL = process.argv.find(arg => arg.startsWith(`${PROTOCOL}://`)); if (targetURL) { handleOpenURL(null, targetURL); } } } // 申请单实例锁 const gotSingleInstanceLock = app.requestSingleInstanceLock(); if (!gotSingleInstanceLock) { // 已有实例在运行,直接退出新实例 app.quit(); } else { // Windows下后续唤起应用时触发该事件 app.on('second-instance', (event, commandLine) => { if (process.platform === 'win32') { const targetURL = commandLine.find(arg => arg.startsWith(`${PROTOCOL}://`)); if (targetURL) { handleOpenURL(event, targetURL); } } // 激活已有窗口,提升用户体验 const { mainWindow } = require('./utils/createWindow'); if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); app.on('ready', () => { // 先处理Windows首次启动的命令行参数 parseWinLaunchArgs(); // 创建主窗口 createWindow(`file://${__dirname}/views/welcome.html`); // 注册自定义协议 app.setAsDefaultProtocolClient(PROTOCOL); }); app.on('activate', () => { const { mainWindow } = require('./utils/createWindow'); if (mainWindow === null) { createWindow(`file://${__dirname}/views/welcome.html`); } }); // macOS专属的协议唤起事件 app.on('open-url', handleOpenURL); } // 全平台窗口关闭逻辑(macOS保留dock图标) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });
2. 统一URL处理函数handleOpenURL.js
const { mainWindow } = require('./utils/createWindow'); const createWindow = require('./utils/createWindow'); module.exports = (e, url) => { if (e) e.preventDefault(); // 这里写你的URL解析逻辑,比如提取传递的数据 console.log('收到自定义协议请求:', url); // 根据窗口状态做不同处理 if (mainWindow && mainWindow.isVisible()) { mainWindow.focus(); // 可以通过IPC把URL传给渲染进程处理,比如: // mainWindow.webContents.send('protocol-data-received', url); } else { // 把URL作为参数传给新窗口(如果需要的话) const encodedURL = encodeURIComponent(url); createWindow(`file://${__dirname}/views/welcome.html?protocol=${encodedURL}`); } };
关键细节说明
- 单实例控制:
requestSingleInstanceLock保证同一时间只有一个应用实例,Windows下后续唤起会触发second-instance事件,我们从参数里提取URL即可。 - Windows首次启动适配:首次通过协议打开时,URL在
process.argv里,必须在ready事件触发前解析,不然会错过。 - 逻辑统一:把macOS的
open-url和Windows的两种场景都交给同一个handleOpenURL处理,避免重复代码。 - 用户体验优化:不管哪种方式唤起应用,都要激活已有窗口(如果存在),不能让用户找不到窗口。
这样调整后,你的Electron应用在macOS和Windows上就能一致处理自定义协议了,不管是首次启动还是后续唤起,都能拿到传递的URL数据。
内容的提问来源于stack exchange,提问作者Apal Shah




