“已发送"状态下的推送通知,为何用户的手机上迟迟不见踪影?服务器日志显示HTTP 200成功响应,实际送达率却只有60%?这不是代码bug,而是移动操作系统十五年演进中,在"即时送达"与"电池续航"之间做出的深刻妥协。

推送通知的"幽灵"现象

2024年,一家金融科技公司的运维团队发现了一个诡异的数据:他们的FCM(Firebase Cloud Messaging)后台显示99.2%的消息"成功发送”,但客户端实际接收率却徘徊在58%-73%之间。更令人困惑的是,同一批消息在小米手机上的接收率只有41%,而在Google Pixel上却高达89%。

这不是孤例。根据Push Amplification 2023年的基准测试,在东南亚市场的Android设备上,推送通知的到达率呈现剧烈波动:Samsung设备平均87%,Xiaomi设备62%,OPPO设备58%,Vivo设备54%。同一套服务端代码,同样的消息内容,结果却天差地别。

xychart-beta
    title "Android设备推送到达率对比 (Push Amplification 2023)"
    x-axis ["Pixel", "Samsung", "Xiaomi", "OPPO", "Vivo"]
    y-axis "到达率 (%)" 0 --> 100
    bar [89, 87, 62, 58, 54]

根源不在于代码质量,而在于推送通知系统的底层架构——一套在过去十五年间不断叠加的复杂妥协机制。

从持久连接说起:推送通知的技术本质

要理解延迟,必须先理解推送通知是如何工作的。核心机制是持久连接(Persistent Connection)。

当你的手机开机后,操作系统会主动建立一条到推送服务器的长连接。这条连接并非"一直活跃"——那会瞬间耗尽电池——而是处于"半休眠"状态。APNs(Apple Push Notification service)和FCM都采用类似的架构:TCP socket保持打开,但只有在有数据传输时才唤醒无线电。

sequenceDiagram
    participant App as 应用服务器
    participant Push as 推送服务(APNs/FCM)
    participant OS as 操作系统
    participant Device as 用户设备
    
    Note over Device,Push: 持久连接(半休眠状态)
    
    App->>Push: POST /send (设备Token + 消息体)
    Push->>Push: 认证验证
    Push-->>App: 200 OK (消息已接受)
    
    Note over Push: 消息排队,等待设备可达
    
    Push->>OS: 通过持久连接推送
    OS->>Device: 唤醒应用处理
    Device->>Device: 显示通知/执行后台任务

这个架构的精妙之处在于:应用服务器无需知道设备的IP地址,也无需维护与设备的直接连接。所有复杂性都由平台提供商(Apple或Google)接管。

但这也意味着:当推送服务返回"成功"时,它只表示"消息已被接受进入队列",而非"已送达设备"。这是第一个认知误区。

APNs vs FCM:架构差异对比

两大平台的推送架构有本质区别:

flowchart TB
    subgraph iOS["iOS推送架构"]
        iOS_App[应用服务器] --> APNs
        APNs --> iOS_Dev[设备]
        iOS_Dev --> iOS_Handler[应用处理]
    end
    
    subgraph Android["Android推送架构"]
        Android_App[应用服务器] --> FCM
        FCM --> GPS[Google Play Services]
        GPS --> Android_Dev[设备]
        Android_Dev --> Android_Handler[应用处理]
    end
    
    style iOS fill:#e1f5fe
    style Android fill:#fff3e0

APNs架构特点

  • 设备Token:每个应用在每台设备上获得唯一的Token,作为"地址"。Token由APNs生成并定期更新。
  • HTTP/2多路复用:单个TCP连接可以并行发送多个请求,大幅提升吞吐量。
  • Token-based认证:使用JWT(JSON Web Token)替代传统的证书认证,一个密钥可服务于多个应用。
  • 存储转发:如果设备离线,APNs会存储消息(最多30天),待设备上线后投递。

FCM架构特点

  • 连接共享:Google Play Services维护着一条到FCM服务器的共享TCP连接。无论设备上安装了多少个使用FCM的应用,始终只有这一条socket在运行。
  • 优先级系统:高/普通优先级区分,影响Doze模式下的行为。
  • 消息折叠:相同collapse_key的消息只保留最新一条。

FCM的连接共享设计对电池极其友好:一条空闲的TCP连接几乎不消耗电量。但代价是所有应用的推送都依赖同一个系统服务——如果Play Services被限制,所有推送都会受影响。

延迟的第一层:操作系统电源管理

推送通知延迟的最主要原因,是操作系统为延长电池寿命而实施的激进限制。

Android Doze模式深度解析

2015年,Android 6.0 Marshmallow引入了Doze模式。当设备静止、屏幕关闭且未充电时,系统会逐渐进入深度休眠:

timeline
    title Android Doze模式演进时间线
    section Light Doze
        屏幕关闭5分钟 : 进入Light Doze
        : 网络访问受限
        : 维护窗口约5分钟间隔
    section Deep Doze
        静止30分钟+未充电 : 进入Deep Doze
        : 网络完全阻断
        : 维护窗口逐渐延长
    section 极深度Doze
        数小时后 : 维护窗口可达数小时
        : 高优先级消息也可能延迟

在Deep Doze期间,所有普通优先级的FCM消息都会被暂停投递,直到下一个维护窗口。维护窗口的间隔从30分钟逐渐延长到数小时。

高优先级消息可以突破Doze限制——但有限度。FCM会检测你的应用是否"滥用"高优先级。

FCM优先级降级机制

这是最容易被忽视的延迟原因。FCM使用过去7天的消息行为来判断是否应该"降级"某个应用的高优先级消息:

flowchart TD
    A[发送高优先级消息] --> B{检测7天内行为}
    B --> C[记录是否产生用户可见通知]
    C --> D{高优先级消息通知转化率}
    D -->|低于阈值| E[自动降级为普通优先级]
    D -->|高于阈值| F[保持高优先级]
    E --> G[消息延迟至维护窗口]
    F --> H[即时投递]
    
    style E fill:#ffcdd2
    style F fill:#c8e6c9

降级逻辑:

  1. FCM记录每条高优先级消息是否最终导致了用户可见的通知。
  2. 如果检测到模式——例如大量高优先级消息没有产生通知——系统会自动将该应用的后续消息降级为普通优先级。
  3. 降级是针对每个应用实例独立决定的,不同用户可能有不同体验。

iOS的Background Fetch限制

iOS没有类似Doze的显式模式,但有其独特的限制:

  • 系统可能完全忽略静默推送。
  • 低电量模式下静默推送会被阻断。
  • 如果应用被用户强制关闭,静默推送无法唤醒它。
xychart-beta
    title "iOS vs Android 推送Opt-in率对比"
    x-axis ["iOS", "Android"]
    y-axis "Opt-in率 (%)" 0 --> 100
    bar [47, 86]

根据Pushwoosh的2025年基准研究,iOS推送通知的opt-in率(用户允许推送的比例)约为43%-51%,显著低于Android的81%-91%。

延迟的第二层:OEM厂商的"优化"

如果说Doze是Google的官方限制,那么各大Android厂商的定制ROM就是一场"黑暗森林"。

flowchart LR
    subgraph OEM["OEM厂商限制对比"]
        direction TB
        MIUI[小米MIUI/HyperOS<br/>激进杀死后台进程<br/>30分钟后限制]
        EMUI[华为EMUI<br/>后台活动管理<br/>等级化限制]
        OPPO[OPPO/Vivo/Realme<br/>应用冻结<br/>5-10分钟后暂停]
        Samsung[三星One UI<br/>自适应电池<br/>ML预测限制]
    end
    
    style MIUI fill:#ffebee
    style EMUI fill:#e3f2fd
    style OPPO fill:#f3e5f5
    style Samsung fill:#e8f5e9

小米MIUI/HyperOS

小米是最激进的背景进程杀手。MIUI的"神隐模式"和"智能省电"会在应用进入后台后约30分钟开始限制其活动,随后可能直接杀死进程。

更隐蔽的是自启动限制:即使应用有正当理由在后台运行(如即时通讯),MIUI也会默认禁止其自启动。用户必须手动在设置中为每个应用开启"自启动"权限。

华为EMUI

华为的限制更为系统化。其"后台活动管理"将应用分为不同等级,默认情况下第三方应用被归类为"可限制"级别。在设备进入空闲状态后,这类应用的网络访问会被逐步收紧。

华为还实现了"受保护应用"列表机制——只有用户手动添加到列表中的应用才能在后台保持活跃。

OPPO/Vivo/Realme

这些品牌共享类似的限制策略,统称为"后台清理"或"应用冻结"。其特点是:

  • 应用在后台5-10分钟后可能被暂停。
  • 用户最近任务界面划掉应用 = 强制停止,推送服务彻底中断。
  • 需要在多个设置层级中逐个开启权限:“自启动”、“后台活动”、“电池无限制”。

三星One UI

三星的"自适应电池"(Adaptive Battery)基于机器学习预测用户行为。问题在于:如果用户很少打开某个应用,系统会认为该应用的推送"不重要",从而延迟甚至阻断其后台活动。

延迟的第三层:消息优先级与配额

推送服务本身也有流量控制机制,防止滥用和保护系统稳定性。

FCM配额限制

限制类型 阈值 触发后果
项目级消息速率 60万条/分钟 HTTP 429
设备级消息速率 240条/分钟/设备 消息丢弃
设备级消息速率 5000条/小时/设备 消息丢弃
可折叠消息存储 20条/设备 延迟投递

APNs限制

Apple不公开具体的速率限制,但会返回HTTP 429 Too Many Requests作为信号。更常见的限制是payload大小:APNs要求payload不超过4KB(4096字节),超出会返回HTTP 413 Payload Too Large。

APNs的错误响应包含详细的reason字段,如:

  • BadDeviceToken:Token无效,应从数据库移除。
  • Unregistered:设备已注销Token。
  • DeviceTokenNotForTopic:Token与应用不匹配。

延迟的第四层:网络环境

推送通知的"最后一公里"受网络条件影响显著。

网络切换问题

当设备从Wi-Fi切换到移动数据(或反之),FCM的持久连接会中断。重建连接需要时间,期间的消息可能被延迟。

更复杂的是运营商NAT的更新:移动网络的IP地址可能频繁变化,导致推送服务器与设备之间的连接"假死"——连接状态显示正常,但实际已无法通信。

中国大陆的特殊挑战

flowchart TB
    subgraph 海外["海外市场"]
        App1[应用服务器] --> FCM1[FCM]
        FCM1 --> GPS1[Google Play Services]
        GPS1 --> Device1[设备]
    end
    
    subgraph 中国大陆["中国大陆市场"]
        App2[应用服务器] --> PushAgg[第三方推送聚合服务]
        PushAgg --> MiPush[小米推送]
        PushAgg --> HwPush[华为推送]
        PushAgg --> OPPOPush[OPPO推送]
        PushAgg --> VivoPush[Vivo推送]
        MiPush --> Device2[小米设备]
        HwPush --> Device3[华为设备]
        OPPOPush --> Device4[OPPO设备]
        VivoPush --> Device5[Vivo设备]
    end
    
    style 海外 fill:#e8f5e9
    style 中国大陆 fill:#fff8e1

FCM在中国大陆基本不可用,原因是Google服务被屏蔽。解决方案包括:

  • 厂商推送通道:小米推送、华为推送、OPPO推送、VIVO推送——每个厂商有自己的推送服务,需要在应用中集成多个SDK。
  • 第三方推送服务:如极光推送、个推,它们聚合了多个厂商通道。
  • 自建推送:使用MQTT或其他协议建立自有推送通道,但需要处理后台保活问题。

延迟的第五层:静默推送的陷阱

静默推送(silent notification)用于在不打扰用户的情况下唤醒应用执行后台任务。但它的限制最容易被低估。

iOS静默推送限制

Apple的技术文档明确指出:

The system may throttle the delivery of background notifications if the total number becomes excessive.

实践中,iOS对静默推送的频率限制大约是每小时2-3次。超出后,系统会延迟甚至丢弃后续的静默推送。

Android数据消息

在Android上,纯数据消息(不含notification字段的FCM消息)需要应用在前台运行或处于活跃状态才能立即处理。如果应用被杀死或处于Doze,数据消息会被延迟到下一个维护窗口。

延迟的第六层:Token管理失效

设备Token是推送系统的"地址",但这个地址会过期。

stateDiagram-v2
    [*] --> Active: Token注册成功
    Active --> Validating: 定期验证
    Validating --> Active: 验证通过
    Validating --> Expired: 验证失败
    Active --> Stale: 设备长时间离线
    Stale --> Active: 设备重新上线
    Stale --> Expired: 28天未活跃(FCM)
    Expired --> [*]: 从数据库移除
    
    note right of Active
        正常工作状态
        可接收推送
    end note
    
    note right of Stale
        Token可能失效
        消息被延迟
    end note
    
    note right of Expired
        Token已失效
        消息被丢弃
    end note

Token失效的场景

场景 iOS Android
应用卸载重装 Token变化 Token变化
系统升级 可能变化 通常不变
用户清除应用数据 可能变化 Token变化
设备恢复出厂设置 Token变化 Token变化
Token长期未使用 APNs可能注销 FCM 28天后标记设备不活跃

FCM的"设备不活跃"机制值得特别注意:如果一个设备连续28天没有收到任何消息(或无法送达),FCM会将其标记为不活跃,后续消息会被直接丢弃,不会进入队列。

优化策略:从架构到实现

理解延迟的根源后,可以采取系统性的优化措施。

优先级策略

不要默认使用高优先级。FCM的优先级降级机制意味着滥用会导致更严重的后果。

消息类型 优先级 理由
即时通讯 用户期望即时响应
交易通知 金融相关,时效性强
社交动态 普通 可接受数分钟延迟
内容更新 普通 后台同步,无需立即
营销推送 普通 避免触发用户反感

回退机制

推送通知不是100%可靠的通信渠道。关键业务应该有备用方案:

flowchart TD
    A[发送推送] --> B{30秒内送达?}
    B -->|是| C[完成]
    B -->|否| D[检查送达状态]
    D --> E{设备在线?}
    E -->|是| F[提升优先级重试]
    E -->|否| G[启用备用通道]
    G --> H[应用内通知]
    H --> I{用户24小时内打开应用?}
    I -->|否| J[发送邮件/SMS]
    I -->|是| C
    F --> K{重试成功?}
    K -->|是| C
    K -->|否| G

监控与可观测性

FCM提供了Aggregate Delivery Data API,可以获取消息的聚合送达统计:

  • 送达成功
  • 待处理(设备离线)
  • 被折叠
  • TTL过期
  • 应用被强制停止
  • 设备不活跃

监控这些指标可以快速定位问题。例如,如果"设备不活跃"比例持续上升,说明Token管理需要优化;如果"TTL过期"比例高,说明用户群体中有大量长期不活跃设备。

技术演进的方向

推送通知领域正在经历新的变化。

统一推送标准

中国信息通信研究院牵头制定了"统一推送接口标准",旨在解决厂商碎片化问题。如果广泛采用,开发者只需对接一个接口,系统自动路由到厂商通道。

Android 14的变化

Android 14进一步加强了前台服务的限制,但同时提供了"面向用户的通知"(user-facing notification)的概念——如果通知会在短时间内显示给用户,系统会允许应用短暂突破限制。

APNs的演进

Apple在iOS 17中引入了Live Activities,允许在锁屏上实时更新活动状态(如外卖进度)。这为需要持续更新的场景提供了比传统推送更优雅的解决方案。

技术权衡的本质

推送通知的延迟问题,本质上是"即时性"与"电池续航"之间的永恒博弈。

操作系统的立场很清晰:默认保护电池,只对"真正重要"的消息给予优先权。开发者的挑战在于:如何让系统相信你的消息确实重要?

答案不在于技术技巧,而在于产品设计:

  • 减少不必要的推送频率。
  • 确保每条推送都有用户价值。
  • 正确使用优先级和消息类型。
  • 建立健全的Token管理机制。

推送通知不是魔法,而是精心设计的工程系统。理解其工作原理,才能在"送达率"与"用户体验"之间找到平衡。


参考文献

  1. Apple Developer Documentation. Establishing a connection to Apple Push Notification service (APNs).
  2. Firebase Documentation. Set and manage Android message priority.
  3. Firebase Documentation. FCM Throttling and Quotas.
  4. Android Developers. Optimize for Doze and App Standby.
  5. Firebase Blog. Understanding FCM Message Delivery on Android, 2024.
  6. Pushwoosh. Push notification benchmarks 2025.
  7. Push Amplification. Push Notification Delivery Benchmarks | Southeast Asia, 2023.
  8. Braze. Push deliverability for Chinese Android Devices.
  9. Clix Blog. How Push Notification Delivery Works Internally: APNs and FCM, 2025.
  10. Fegno. Common Push Notification Delivery Issues And How To Fix Them.
  11. Wikipedia. Apple Push Notification service.
  12. Reddit r/androiddev. That moment you realize half your FCM/APNs pushes are going undelivered, 2025.
  13. Medium. Why Your App’s Notifications Get Delayed in Android, 2025.
  14. Firebase Blog. Life of a message from FCM to the device, 2019.
  15. Apple WWDC 2016. What’s New in the Apple Push Notification Service.