人人都会AI编程

12.4 自定义协议注册:唤起应用、协议传参

更新时间:2026-07-11

自定义协议(Custom Protocol)是让桌面应用融入操作系统交互流程的强力手段。通过注册一个类似 myapp:// 的专属协议,你的用户就可以通过浏览器链接、命令行或第三方程序直接唤起你的应用,并向其传递参数。例如,VS Code 支持 vscode://file/路径 来打开指定文件,Zoom 通过 zoommtg:// 链接直接加入会议。

在 Electron 应用中实现这一能力,主要分为三步:注册协议、处理传参、安全加固。

12.4.1 注册协议

使用 app.setAsDefaultProtocolClient(protocol, [path, args]) 将你的应用设置为指定协议的默认处理程序。调用时机通常在 app.whenReady() 之后,或者应用首次启动时进行一次。

需要留意以下几点:

  • 协议名只能包含小写字母、数字和横线,且不能以横线开头(例如 my-appmyapp2)。操作系统对协议名的规则可能略有差异,但 Electron 在内部做了兼容性屏蔽。
  • 在 macOS 上,如果你希望一个链接点击后直接激活已运行的应用实例,而不启动新实例,可以配合 app.requestSingleInstanceLock() 使用(12.3 节已有介绍)。
  • 调用此方法会在操作系统注册表(Windows)、Info.plist(macOS)、.desktop 文件(Linux)中写入相应信息,因此通常只需在应用安装或第一次运行时执行一次。不过 Electron 本身并没有内置“仅首次注册”的机制,你需要自己用一种持久化标记来判断是否需要重复调用。

一个安全的做法是每次启动时都尝试注册,因为如果已经是默认协议处理程序,Electron 会直接返回 true,不会导致额外的副作用。此方法的签名还支持传入 path(例如 electron apps) 和 args(如 -- 参数),但在常规使用中,我们只需要指定协议字符串。

基础示例(main 进程):

const { app } = require('electron');
const protocol = 'myapp'; // 自定义协议名

app.whenReady().then(() => {
  // 注册协议,若已注册则静默返回 true
  const isDefault = app.setAsDefaultProtocolClient(protocol);
  console.log(`是否已设为默认 ${protocol} 处理程序:`, isDefault);
});

12.4.2 接收和处理协议链接

不同操作系统在触发自定义协议时的行为有细微差异,尤其是在应用已运行与冷启动两种场景下,但 Electron 提供了统一的 API 来处理。

场景与事件:

  1. macOS:无论应用是否已经运行,点击协议链接时,系统都会通过 app 模块的 open-url 事件将完整的 URL 传递给应用。你需要监听这个事件来获取传参。
  1. Windows 与 Linux:协议触发会尝试启动一个新的应用进程。为避免重复进程,常见的做法是配合 app.requestSingleInstanceLock(),然后在 second-instance 事件中捕获第二个实例收到的命令行参数(其中包含协议 URL),并将其转发给主窗口进行处理。
  1. 冷启动(应用未运行):在 macOS 上,open-url 事件同样会正常触发;在 Windows/Linux 上,应用启动时的命令行参数中会包含协议的 URL,你可以通过 process.argv 获取,但最好结合单实例锁来统一处理,避免碎片化。

综合来看,一个鲁棒的处理策略是:始终使用单实例锁,并在 second-instanceappopen-url (macOS) 事件中解析 URL,最后交给主窗口的渲染进程。

实现步骤(main 进程):

const { app, BrowserWindow, ipcMain } = require('electron');
const protocol = 'myapp';
let mainWindow = null;

// 单实例锁
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (event, commandLine, workingDirectory) => {
    // 在 Windows/Linux 下,命令行包含协议 URL
    const url = commandLine.find(arg => arg.startsWith(`${protocol}://`));
    if (url && mainWindow) {
      // 通过 IPC 通知渲染进程处理链接
      mainWindow.webContents.send('protocol-url', url);
      // 当应用被其他实例唤起时,将窗口提升到前台
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
    }
  });

  app.whenReady().then(() => {
    mainWindow = createWindow();
    // 注册协议
    app.setAsDefaultProtocolClient(protocol);

    // macOS 专用:监听 open-url 事件
    app.on('open-url', (event, url) => {
      event.preventDefault(); // 阻止默认行为(新开窗口)
      if (mainWindow) {
        mainWindow.webContents.send('protocol-url', url);
        if (mainWindow.isMinimized()) mainWindow.restore();
        mainWindow.focus();
      }
    });

    // 处理冷启动时的协议链接(Windows/Linux)
    const protocolUrl = process.argv.find(arg => arg.startsWith(`${protocol}://`));
    if (protocolUrl && mainWindow) {
      mainWindow.webContents.once('did-finish-load', () => {
        mainWindow.webContents.send('protocol-url', protocolUrl);
      });
    }
  });
}

渲染进程接收参数:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
  onProtocolUrl: (callback) => ipcRenderer.on('protocol-url', (event, url) => callback(url)),
});

// renderer.js (或 Vue/React 组件内)
window.electronAPI.onProtocolUrl((url) => {
  console.log('收到协议链接:', url);
  // 解析 URL 提取需要的参数,例如 myapp://action/param
  const parsed = new URL(url);
  const action = parsed.hostname;  // 对应 myapp://host/path 的结构
  const path = parsed.pathname;
  // 执行对应的业务逻辑,如打开特定页面、导入文件等
});

12.4.3 协议格式设计

为保证可读性和可扩展性,建议采用类似 HTTP URL 的结构:scheme://host/path?query=value。例如 myapp://open-file?path=/Users/me/doc.pdfmyapp://settings/account。这种格式可以直接使用 JavaScript 的 URL 构造函数解析,简洁且不容易出错。

同时也可以在应用安装包中通过配置文件(Windows 的 NSIS 安装脚本、macOS 的 plist 文件)让系统自动关联协议,但 Electron 的运行时注册更为方便,适合开发阶段与快速发布。

12.4.4 安全注意事项

  • 验证来源:协议链接可以被任何网页或程序触发,在渲染进程处理之前,主进程最好对 URL 进行基本的白名单验证,只允许预期的 host 和路径模式。例如,仅允许 myapp://safe-action/*
  • 避免远程代码执行:永远不要直接 eval() 或者类似动态执行的方式处理传入的参数。将参数看作不可信数据,只用于导航或显示,不拼接到脚本中。
  • 单实例与焦点保护:确保 second-instance 处理时能正确恢复窗口,避免用户点击链接后应用无反应或出现多个窗口互相覆盖。

12.4.5 实战验证

完成开发后,你可以在任意浏览器地址栏输入你注册的协议(例如 myapp://test/hello)并按回车,如果系统弹出协议启动确认(首次需要用户批准,macOS 和 Windows 均有安全提示),你的应用就会被唤起并接受到参数。这对于实现外部系统集成(如 OAuth 回调、通过网页打开本地工具)极为实用。

总结而言,自定义协议注册是 Electron 打通线上与线下体验的关键桥梁。只需十几行核心代码,就能让你的桌面应用像原生程序一样被外部链接激活,并携带上下文跳转到指定功能,极大提升了用户的工作流连贯性。