大多数开发者对Push通知的理解,停留在"调用API发送消息"的表面。但我们很少深入思考:为什么用户关闭浏览器后还能收到通知?这背后的通信机制到底是什么?为什么你的推送打开率这么低?
这篇文章,我们会从源码级别剖析Service Worker、Push API和Notification API的协作原理,以及大厂(字节、阿里云)推荐的实现思路。
一个让人困惑的现象你是否遇到过这样的情况:
调用了Notification.requestPermission(),用户也点了允许,但推送消息要么没收到,要么收到了没人点看似完整的推送流程,在真实生产环境中就是"失效"同样的代码,字节跳动员工的App推送打开率是你的3倍这不是巧合。问题的根源在于:大多数开发者对Push通知的实现停留在"Copy-Paste代码"阶段,从未理解它在浏览器层面的真实工作机制。
Push通知的真实工作流:看不见的三层架构让我先画出完整的消息流转过程:
代码语言:javascript复制┌─────────────────────────────────────────────────────────────────┐
│ 用户点击"允许通知"的那一刻,发生了什么? │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [浏览器进程] [Service Worker进程] │
│ │ │ │
│ 1. 加载SW.js 1. 独立的后台进程 │
│ │ 2. 持久化存活 │
│ 2. 建立Push订阅 3. 监听push/notification事件 │
│ │ │ │
│ └──→ 获取VAPID密钥 ────────────┘ │
│ │ │
│ ├─→ 生成subscription对象 │
│ │ { │
│ │ endpoint: "https://fcm.google.com/...", │
│ │ keys: { p256dh, auth } │
│ │ } │
│ │ │
│ └─→ 发送到服务器保存 │
│ (关键!没有这一步就无法推送) │
│ │
│ [推送服务器](第三方:Google FCM、Apple APNs等) │
│ │ │
│ └─→ 加密消息 ──→ 推送给用户设备 ──→ 触发SW push事件 │
│ │
└─────────────────────────────────────────────────────────────────┘
这个图揭示了一个90%的开发者忽视的问题:
Push订阅对象必须存储在服务器。 如果没有这一步,再完美的前端代码也无法工作。这正是为什么很多人"按教程做了但还是收不到推送"。
第一层:Service Worker注册 — 隐形的守护进程想象Service Worker就像你手机后台运行的应用。即使你关闭浏览器,它仍然可以收信息。
代码语言:javascript复制// ✅ 标准的注册方式(99%的教程都是这样写)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker作用域:', registration.scope);
// 重要:记住这个registration对象,后续的push订阅需要它
})
.catch(error => {
console.error('注册失败:', error);
});
}
但这里有个坑: 上面的代码只是注册,并不能保证SW已经完全启动。如果你立刻去调用pushManager.subscribe(),很可能会报错。
正确做法是这样的:
代码语言:javascript复制// ✅ 确保Service Worker已准备就绪
navigator.serviceWorker.ready.then(registration => {
console.log('SW已准备好,可以安全地进行下一步');
// 在这里进行push订阅操作
});
关键词:**ready不是register**。很多人混淆这两个概念,导致竞态条件问题。
为什么Service Worker这么重要?让我们看看浏览器对SW的内存管理机制:
代码语言:javascript复制浏览器生命周期管理:
[Page Active] [Page Closed] [Browser Closed]
│ │ │
├─ 页面SW活跃 ├─ SW仍在运行 ├─ SW持久化
│ (优先级最高) │ (中等优先级) │ 保存状态
│ │ │
└─ 可监听所有事件 └─ 只监听push/ └─ 系统通知
notification事件 触发SW
这就是为什么只有Service Worker能接收来自推送服务的消息。普通的JavaScript(即使在