Electron Notification API 完全指南:主进程桌面通知从创建、交互事件到跨平台分组管理
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
本篇指南围绕 Electron 的Notification类展开,系统讲解主进程桌面通知的创建选项、实例事件(show、click、reply、action、close、failed)、macOS 通知中心历史管理与 Windows 激活回调等全部静态/实例 API,并结合 Electron 仓库中shell/browser/下的 C++ 实现与spec/api-notification-spec.ts测试用例,剖析参数解析、UUID 默认值、Windows 64 字符限制等平台差异的底层来源。读完本文,你将能够编写在各操作系统上表现一致、可分组、可交互、可在应用重启后恢复监听的通知功能。
一、Notification 类概览:只在主进程使用
Notification是一个 EventEmitter,用于在操作系统层面创建桌面通知。它定义在 docs/api/notification.md 中,属于Main(主进程)API:
- 如果希望从渲染进程显示通知,官方建议改用 Web 标准的 Notifications API,两者不可混用。跨进程需求可通过 IPC 桥接(参见 IPC 教程)。
- 与 Web Notification API 不同,
new Notification()构造对象不会立即显示,必须显式调用show()才会出现在操作系统上。 Notification是 Electron 内置类,不能在用户代码中被子类化,相关背景见 FAQ。
注意(原文档说明):在 macOS 上,通知底层基于 UNNotification API。该 API 要求应用经过代码签名后通知才能出现;未签名的二进制在调用通知 API 时会触发
failed事件。
从源码结构看,JS 层只是一个极薄的绑定层。lib/browser/api/notification.ts 全文仅 14 行,它通过process._linkedBinding('electron_browser_notification')拿到 C++ 侧的Notification类,并把isSupported、getHistory、remove、removeAll、removeGroup静态方法逐一挂到 JS 类上;其中handleActivation仅在process.platform === 'win32'且 C++ 绑定暴露该方法时才挂载。真正的实现位于 shell/browser/api/electron_api_notification.cc。
二、静态方法
Notification.isSupported()
返回boolean,表示当前系统是否支持桌面通知。
实现上它检查浏览器客户端能否提供通知 presenter:
// shell/browser/api/electron_api_notification.cc(节选) bool Notification::IsSupported() { return !!static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get()) ->GetNotificationPresenter(); }即isSupported()的结果取决于平台 presenter 是否成功创建(例如 Linux 下依赖 libnotify 环境)。
Notification.handleActivation(callback)Windows
callbackFunctiondetailsActivationArguments - 通知激活详情(类型、原始参数字符串、actionIndex、reply、userInputs等)。
注册一个集中式回调,处理所有通知激活:点击、回复、动作按钮,无论触发来源对应的Notification对象是否还在内存中。该方法自动处理时序问题:
- 若调用前激活已经发生(例如应用因点击通知冷启动),回调会立即带着当时的详情被调用一次;
- 之后的每次激活发生时,回调照常触发;
- 回调持续注册,直到再次调用
handleActivation被替换。
它覆盖的典型场景包括:冷启动(从通知点击拉起应用)、Action Center 中残留通知在应用重启后没有内存对象、Notification对象被 GC、以及对象仍存活(此时回调与实例事件同时触发)。
const { Notification, app } = require('electron') app.whenReady().then(() => { // Register handler for all notification activations Notification.handleActivation((details) => { console.log('Notification activated:', details.type) if (details.type === 'reply') { console.log('User reply:', details.reply) } else if (details.type === 'action') { console.log('Action index:', details.actionIndex) } }) })源码佐证:在 electron_api_notification.cc#L451-L463 中,HandleActivation用v8::Global<v8::Function>(配合base::NoDestructor)持久持有 JS 回调以避免被 GC,再通过electron::SetActivationHandler把 C++ 激活事件桥接到 JS;ActivationArguments到 JS 对象的转换逻辑位于 electron_api_notification.cc#L396-L420,只有type === 'action'时写入actionIndex,type === 'reply'时写入reply,userInputs非空时才附加。
Notification.getHistory()macOS
返回Promise<Notification[]>,解析出当前仍存在于通知中心的全部已投递通知。每个返回的Notification都是与对应已投递通知相连的活对象:用户在通知中心与之交互时,click、reply、action、close事件会正常触发——这使应用重启后可以重新挂接事件处理器。
- 返回对象仅填充通知中心可得的信息:
id、groupId、title、subtitle、body;actions、silent、icon等其他属性为默认值。 - 与
new Notification()创建的普通通知不同,getHistory()返回的通知不会因为对象被 GC 而从通知中心消失。对其调用show()会把通知中心中的原通知移除,并以相同属性重新发布一条新通知。 - 同受代码签名约束:未签名的开发构建中通知不会投递到通知中心,该方法将解析为空数组。
const { Notification, app } = require('electron') app.whenReady().then(async () => { // Restore notifications from a previous session const notifications = await Notification.getHistory() for (const n of notifications) { console.log(`Found delivered notification: ${n.id} - ${n.title}`) n.on('click', () => { console.log(`User clicked: ${n.id}`) }) n.on('reply', (event) => { console.log(`User replied to ${n.id}: ${event.reply}`) }) } // Keep references so events continue to fire })实现上,electron_api_notification.cc#L466-L529 中的GetHistory调用 presenter 的GetDeliveredNotifications异步回调,把每条NotificationInfo(id/title/subtitle/body/group_id,见 shell/browser/notifications/notification.h#L63-L76)包装成一个以NotificationInfo专用构造器创建的对象,再调用平台通知的Restore()把交互事件路由过来。
Notification.remove(id)macOS
id(string | string[]) - 要移除的通知标识符,对应构造函数中的id值。
按标识符从通知中心移除一条或多条已投递通知:
const { Notification } = require('electron') // Remove a single notification Notification.remove('my-notification-id') // Remove multiple notifications Notification.remove(['msg-1', 'msg-2', 'msg-3'])从源码看(electron_api_notification.cc#L531-L561),参数既可以是字符串也可以是字符串数组;传参缺失或类型不符会抛出Expected a string or array of strings错误——这一点在 spec/api-notification-spec.ts#L423-L445 中有逐项验证(空字符串与空数组不抛错,数字参数抛错)。
Notification.removeAll()macOS
移除该应用在通知中心中的所有已投递通知:
const { Notification } = require('electron') Notification.removeAll()Notification.removeGroup(groupId)macOS
groupIdstring - 通知组标识符,对应构造函数中的groupId值。
移除通知中心中所有具有给定groupId的已投递通知:
const { Notification } = require('electron') // Remove all notifications in the 'chat-thread-1' group Notification.removeGroup('chat-thread-1')三、构造函数:new Notification([options])
optionsObject (optional)
| 选项 | 类型 | 平台 | 说明 |
|---|---|---|---|
id | string | macOS, Windows | 通知唯一标识。macOS 映射到UNNotificationRequest的identifier,Windows 映射到 toast 的Tag。不提供或传空字符串时默认随机 UUID。配合Notification.remove()/Notification.getHistory()使用。 |
groupId | string | macOS, Windows | 组标识符,用于在通知中心 / Action Center 中视觉分组。macOS 映射UNNotificationContent.threadIdentifier,Windows 映射 toast 的Group。配合Notification.removeGroup()使用。 |
groupTitle | string | Windows | 组标题。与groupId同时提供时,Windows 会在分组通知上方显示一个标题头(对应 toast 的header元素)。 |
title | string | 全平台 | 通知标题,显示在通知窗口顶部。 |
subtitle | string | macOS | 副标题,显示在标题下方。 |
body | string | 全平台 | 正文,显示在标题或副标题下方。 |
silent | boolean | 全平台 | 是否抑制通知声音。 |
icon | string | NativeImage | 全平台 | 通知图标。传字符串时必须是本地图标文件的有效路径。 |
hasReply | boolean | macOS, Windows | 是否添加内联回复输入框。 |
timeoutType | string | Linux, Windows | 超时时长:'default'或'never'。 |
replyPlaceholder | string | macOS, Windows | 内联回复输入框的占位文本。 |
sound | string | macOS | 通知显示时播放的声音文件名。 |
urgency | string | Linux, Windows | 紧急级别:'normal'、'critical'或'low'。 |
actions | NotificationAction[] | macOS, Windows | 附加动作。类型支持矩阵与限制见NotificationAction文档。 |
closeButtonText | string | macOS | 自定义关闭按钮文案;空字符串使用系统本地化默认文本。 |
toastXml | string | Windows | 自定义 Toast XML,覆盖以上所有属性,提供对设计行为的完全控制。 |
注意(原文档说明):在 Windows 上,
urgency为'critical'只会把通知排到 Action Center 更高位置(高于默认优先级通知),但不会阻止自动消失;要阻止自动消失还需把timeoutType设为'never'。
构造参数的解析与校验(源码级)
electron_api_notification.cc#L140-L168 展示了构造器如何用gin::Dictionary逐项取出id、groupId、groupTitle、title、subtitle、body、icon、silent、replyPlaceholder、urgency、hasReply、timeoutType、actions、sound、closeButtonText、toastXml,并且:
if (id_.empty()) id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();即「未提供或为空字符串时默认随机 UUID」的行为正是此处实现,spec/api-notification-spec.ts#L40-L61 用正则^[0-9a-f]{8}-...验证了这一点。
Windows 平台的额外约束在Notification::New(electron_api_notification.cc#L193-L225)中强制:
app未就绪时抛错Cannot create Notification before app is ready;id/groupId超过64 个 UTF-16 字符(对应 Windows toastTag/Group上限)时抛错;- 设置了
groupTitle却未设置groupId时抛错。
这些校验与 spec/api-notification-spec.ts#L130-L173 的测试一一对应(65 字符抛错、64 字符接受、groupTitle requires groupId to be set)。
最小可用示例
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.show() })一个更完整的窗口场景可参考官方 Fiddle 示例 docs/fiddles/features/notifications/,其讲解见 通知教程。
四、实例事件
由new Notification创建的对象会发出以下事件(部分事件仅限特定操作系统,标注于名称后):
Event:'show'
eventEvent
通知向用户显示时发出。由于show()可重复调用(每次会销毁旧通知并创建属性相同的新通知),该事件可能触发多次。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.on('show', () => console.log('Notification shown!')) n.show() })Event:'click'
eventEvent
用户点击通知时发出。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.on('click', () => console.log('Notification clicked!')) n.show() })Event:'close'
detailsEvent<>reasonWindowsstring (optional) - 关闭原因:'userCanceled'、'applicationHidden'或'timedOut'。
通知被用户手动干预关闭时发出。该事件不保证在所有关闭场景下都触发。
在 Windows 上,close事件有三种触发途径:程序调用notification.close()、用户关闭通知、系统超时。若通知在首次close事件发出后仍存在于 Action Center,再次调用notification.close()会将其从 Action Center 移除,但不会再次发出close事件。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.on('close', () => console.log('Notification closed!')) n.show() })源码上,NotificationClosed(electron_api_notification.cc#L320-L337)区分了有无reason:为空时直接Emit("close"),否则构造带reason字段的事件对象——这正是「reason仅在 Windows 出现」的底层原因(Windows presenter 传入了具体原因,macOS/Linux 不传)。
Event:'reply'macOSWindows
detailsEvent<>replystring - 用户在回复输入框中键入的文本。
replystringDeprecated
当用户在带hasReply: true的通知上点击 “Reply” 按钮时发出。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Send a Message', body: 'Body Text', hasReply: true, replyPlaceholder: 'Message text...' }) n.on('reply', (e, reply) => console.log(`User replied: ${reply}`)) n.on('click', () => console.log('Notification clicked')) n.show() })Event:'action'macOSWindows
detailsEvent<>actionIndexnumber - 被触发的动作索引。selectionIndexnumberWindows- 用户选中项的索引;未选择时为 -1。
actionIndexnumberDeprecatedselectionIndexnumberWindowsDeprecated
const { Notification, app } = require('electron') app.whenReady().then(() => { const items = ['One', 'Two', 'Three'] const n = new Notification({ title: 'Choose an Action!', actions: [ { type: 'button', text: 'Action 1' }, { type: 'button', text: 'Action 2' }, { type: 'selection', text: 'Apply', items } ] }) n.on('click', () => console.log('Notification clicked')) n.on('action', (e) => { console.log(`User triggered action at index: ${e.actionIndex}`) if (e.selectionIndex > -1) { console.log(`User chose selection item '${items[e.selectionIndex]}'`) } }) n.show() })关于actions的平台支持矩阵、macOS 上额外按钮需满足「应用已签名 +Info.plist中NSUserNotificationAlertStyle为alert」等限制,以及 Windowsselection下拉动作的完整用法,详见 NotificationAction 结构文档。
Event:'failed'macOSWindows
eventEventerrorstring - 执行show()过程中遇到的错误。
创建/显示原生通知发生错误时发出。macOS 上未签名应用调用通知 API 即属于此类(见开头提示)。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Bad Action' }) n.on('failed', (e, err) => { console.log('Notification failed: ', err) }) n.show() })五、实例方法与实例属性
notification.show()
立即向用户显示通知。与 Web Notification API 不同,new Notification()本身不会显示通知,必须调用本方法。若通知此前已显示过,该方法会先销毁已显示的通知,再创建一条属性完全相同的新通知。
在 macOS 上,对Notification.getHistory()返回的通知调用show(),会把通知中心中的原通知移除并以相同属性重新发布一条。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.show() })源码中Show()的完整流程(electron_api_notification.cc#L356-L386)值得注意:
- 若对象是
getHistory()恢复的通知(is_restored_为 true)则直接返回——避免重复投递; - 先调用
Close()清理上一次显示; - 通过 presenter 的
CreateNotification(delegate_, id_)创建平台通知,把 JS 属性拷贝到 C++ 的NotificationOptions(见 shell/browser/notifications/notification.h#L36-L61),最后调用平台通知的Show(options)。
notification.close()
移除通知。在 Windows 上:通知仍在屏幕上时调用,会使其消失并移出 Action Center;通知已不在屏幕上时调用,则尝试将其从 Action Center 移除。
const { Notification, app } = require('electron') app.whenReady().then(() => { const n = new Notification({ title: 'Title!', subtitle: 'Subtitle!', body: 'Body!' }) n.show() setTimeout(() => n.close(), 5000) })Close()的实现(electron_api_notification.cc#L339-L353)区分了「已被平台 dismiss」(调用Remove())与「尚未 dismiss」(调用Dismiss())两种路径,对应 C++ 基类 notification.h#L83-L92 中Dismiss/Remove的注释:部分平台(包括 Windows)初始移除并不会彻底销毁通知,需要Remove兜底。
实例属性一览
| 属性 | 类型 | 平台 | 说明 |
|---|---|---|---|
notification.id | string | macOS, Windows | 只读。通知唯一标识,构造时确定:来自id选项,未提供则生成 UUID。 |
notification.groupId | string | macOS, Windows | 只读。组标识符;相同groupId的通知在通知中心/Action Center 中视觉分组。 |
notification.groupTitle | string | Windows | 只读。分组标题头文本。 |
notification.title | string | 全平台 | 标题。 |
notification.subtitle | string | 全平台 | 副标题。 |
notification.body | string | 全平台 | 正文。 |
notification.replyPlaceholder | string | 全平台 | 回复输入框占位文本。 |
notification.sound | string | 全平台 | 声音。 |
notification.closeButtonText | string | 全平台 | 关闭按钮文本。 |
notification.silent | boolean | 全平台 | 是否静默。 |
notification.hasReply | boolean | 全平台 | 是否有回复动作。 |
notification.urgency | string | Linux | 'normal'、'critical'或'low';默认'low'(参见 Notify 规范 urgency 级别定义)。 |
notification.timeoutType | string | Linux, Windows | 'default'或'never';设为'never'时通知永不过期,直到调用 API 关闭或用户关闭。 |
notification.actions | NotificationAction[] | 全平台 | 通知动作数组。 |
notification.toastXml | string | Windows | 自定义 Toast XML。 |
其中id、groupId(Windows 下groupTitle)为只读,测试 spec/api-notification-spec.ts#L28-L38 验证了赋值n.id = 'new-id'会抛错;groupId未提供时默认为空字符串(spec/api-notification-spec.ts#L85-L95)。JS 侧属性的 getter/setter 注册集中在 electron_api_notification.cc#L585-L612 的FillObjectTemplate中,与上表一一对应。
六、在 macOS 上播放声音
macOS 上可以指定通知显示时播放的声音:系统「系统偏好设置 > 声音」中的任何默认声音均可使用,也支持自定义声音文件。自定义文件需拷贝到以下位置之一:
- 应用包内(例如
YourApp.app/Contents/Resources) ~/Library/Sounds/Library/Sounds/Network/Library/Sounds/System/Library/Sounds
更多细节参考 Apple 的NSSound文档。
七、平台注意事项
- Windows:应用需要带
AppUserModelID的开始菜单快捷方式及对应的ToastActivatorCLSID。生产环境中使用 Squirrel.Windows 时快捷键会自动配置;开发阶段可能需要手动调用app.setAppUserModelId()(详见 通知教程的 Windows 小节)。通知点击/回复/动作的集中处理使用第二节的Notification.handleActivation()。 - macOS:应用必须代码签名,通知事件才能正确发出;未签名二进制会触发
failed事件。另外,通知内容超过 256 字节会被截断。 - Linux:通知通过
libnotify发送,兼容遵循 Desktop Notifications Specification 的桌面环境(Cinnamon、Enlightenment、Unity、GNOME、KDE)。
八、架构小结与延伸阅读
从源码结构看,Electron 通知模块采用「JS API 绑定层 + 平台 Presenter」的分层设计:
- JS 层 lib/browser/api/notification.ts 仅做绑定挂载;
- API 层 shell/browser/api/electron_api_notification.h 定义
Notification(cppgc 垃圾回收管理),通过NotificationDelegateProxy持有对 C++ 平台通知的WeakPtr,把平台回调转成 JS 事件; - 平台层由
NotificationPresenter(shell/browser/notifications/notification_presenter.h)按平台实现:macOS(notification_presenter_mac.mm,对接通知中心)、Windows(notification_presenter_win.cc,对接 toast/Action Center)、Linux(notification_presenter_linux.cc,对接 libnotify)。isSupported()、getHistory()、remove*等静态方法全部经由当前 presenter 分发。
相关文档与测试:
- Notification API 原文
- NotificationAction 结构 / ActivationArguments 结构
- 渲染进程通知教程 与 Fiddle 示例 docs/fiddles/features/notifications/
- 完整行为测试 spec/api-notification-spec.ts
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考