iOS 与 Android 推送通知:APNs vs FCM 对比
iOS 与 Android 的 APNs 与 FCM 对比:令牌生命周期、载荷限制、投递预期,以及修复丢失推送的实用检查清单。

你在比较什么(以及为什么重要)
APNs(Apple Push Notification service)和 FCM(Firebase Cloud Messaging)是把消息从你的服务器送到手机的传输管道。它们不决定应用收到消息后做什么,但会强烈影响消息是否能到达、到达的速度以及消息应采用的格式。
当有人说一个推送“在 Android 上能收到但在 iOS 上不能”(或反过来),通常不是单一的 bug。iOS 和 Android 在后台工作、电源管理、权限和消息优先级上有不同的处理方式。同一条消息可能被延迟、被新消息替换、静默到达,或者如果应用无法被唤醒来处理它,则根本不会显示。
本比较聚焦于在真实场景中最常导致意外的部分:设备令牌如何随时间变化、你的载荷能有多大和应如何构建、你可以现实地期待怎样的投递,以及常见的通知看似“丢失”的原因。
这篇文章不涉及选择推送供应商的 UI、营销策略或完整的分析流水线搭建。目标是提高可靠性并加速调试。
几个贯穿全文的术语:
- 令牌(Token):APNs 或 FCM 发给设备的设备专属地址,用来发送推送。
- 主题(Topic):一种组地址(主要与 FCM 一起使用),很多设备可以订阅它。
- 频道(Channel):Android 的通知类别,用来控制声音、重要性和行为。
- 合并键(Collapse key):用来用新消息替换旧的未投递消息的方法。
- TTL(time to live):消息在过期前可以等待投递的时间。
把这些基础弄明白能在“简单推送”跨平台表现不一致时节省大量排查时间。
APNs 与 FCM 在高层的工作方式
APNs 和 FCM 都是你服务器与用户手机之间的中间人。应用不能可靠地直接通过互联网把推送送到设备,因此它会把这项工作交给 Apple(APNs)或 Google(FCM),后者已经与设备维持了受信任的连接。
总体流程相似:应用拿到令牌,后端使用该令牌向推送服务发送消息,推送服务再把它路由到设备。
用通俗话说 APNs
在 iOS 上,应用注册远程通知并(通常)向用户请求权限。Apple 随后提供一个设备令牌。你的后端(常称为“提供者”)向 APNs 发送包含该令牌和载荷的推送请求,APNs 决定能否投递并将通知转发给设备。
你的后端需要向 APNs 进行认证,通常使用基于令牌的认证(签名密钥)。旧的设置会使用证书。
用通俗话说 FCM
在 Android 上,应用实例向 FCM 注册并接收注册令牌。你的后端向 FCM 发送消息,FCM 将其路由到正确的设备。根据应用状态和消息类型,FCM 可能会自动显示通知,或将数据交给应用处理。
你的后端使用服务器凭据(API 密钥或服务账号)向 FCM 认证。
你能控制的:应用代码、请求权限的时机、令牌存储、后端逻辑以及你发送的载荷。Apple 和 Google 控制的:传递网络、可达性、限流规则以及很多末端条件,比如省电策略和系统策略。
令牌生命周期:令牌如何签发、刷新与失效
APNs 与 FCM 最大的日常差异是:令牌不是“设置一次永久有效”。把它们当作可能随时变化的地址。
在 iOS 上,APNs 设备令牌与设备、你的应用以及你的 Apple 开发配置相关联。它可能在应用重装、设备恢复、某些系统更新后发生变化,或在开发过程中切换推送环境(沙盒与生产)时变化。
在 Android 上,FCM 注册令牌可能在应用在新设备上恢复、用户清除应用数据、Google 轮换令牌或应用重装时刷新。你的应用应预期有刷新事件并及时将新令牌发送到服务器。
一个简单规则:始终对令牌做 upsert(更新或插入),不要“插入后忘记”。保存令牌时,保留足够的上下文以避免重复或错误目标:
- 用户或账户 ID(如适用)
- 应用包名/环境
- 平台(iOS/Android)
- 令牌值与最后一次看到的时间戳
- 同意状态(是否已授权)
删除也很重要。通常你不是从“卸载”信号中得知令牌无效,而是从投递错误中学到。如果 APNs 返回 Unregistered(通常伴随 410 状态),或 FCM 返回 NotRegistered/Unregistered,立即删除该令牌,别再无穷重试。
一个常见的泄露隐私的错误场景是:用户登出后另一人登录同一台手机。如果你在登出时不清除或重新映射令牌,你可能会把通知发给错误的人,即便投递“成功”。
载荷限制与消息结构差异
APNs 与 FCM 最实际的差异在于你能在消息里放多少内容,以及手机接收后会如何处理它。
大多数团队只依赖一小部分字段:
- 标题与正文
- 徽章计数(iOS)
- 声音(默认或自定义)
- 自定义键值数据(例如
order_id、status)
大小限制:保持推送精简
两个服务都有载荷大小限制,限制包括你的自定义数据。当超出上限时,投递可能失败或消息行为异常。
一个可靠的模式是发送一个简短通知加上一个 ID,然后让应用从后端拉取详情:
示例:不要发送完整订单摘要,而是发送 { "type": "order_update", "order_id": "123" },让应用调用你的 API 获取最新状态。
仅数据消息 vs 通知行为
在 Android 上,带有 “notification” 载荷的 FCM 消息通常在应用后台时由系统直接显示。仅数据消息则交给你的应用代码,但可能被后台限制或电池设置延迟或阻止。
在 iOS 上,提醒(标题/正文)比较直接,但后台更新更严格。后台推送并不能保证你的代码会立即运行。应把它当作刷新提示,而不是实时任务触发器。
如果你需要可靠性,保持载荷简短,包含稳定标识符,并设计应用在打开或恢复时进行状态对账。
投递预期与可能阻止通知的因素
无论是 APNs 还是 FCM,投递都是尽力而为的。提供者会尝试投递你的消息,但它并不保证设备会展示它。
可达性是第一个限制因素。你发送一条带有 TTL 或过期时间的通知。如果设备在该窗口之后上线,推送就会被丢弃。如果 TTL 设得很长,用户可能会在很久之后看到一条过时的提醒,看起来像是错误。
优先级影响投递时机,但这不是免费的升级。高优先级有助于在设备休眠时更快到达时敏感消息,但过度使用会导致限流、电池消耗增加或系统把你的应用视为噪声源。
两套系统都支持合并,使得新消息替换旧的待投递消息而不是叠加。APNs 使用 collapse identifier,FCM 使用 collapse key。如果你对比如 order_status 做合并,用户可能只会看到最新状态,而不会看到每一步。
即使提供者成功投递,手机仍可能阻止用户看到它:
- 勿扰或 Focus 模式会静音或隐藏提醒
- 应用通知设置可能被关闭或设为静默投递
- Android 的通知渠道可能被针对某个类别关闭
- 后台限制或省电模式可能延迟投递
- 如果你的应用发布了很多相似提醒,系统可能抑制重复的提醒
把推送当作不可靠的传输:把重要状态保存在后端,并在应用打开时刷新当前状态,即便通知未显示也能补救。
影响投递的权限与设备设置
很多“投递问题”实际上是权限或设置问题。
在 iOS 上,首次权限提示很重要。如果用户点了“不要允许”,通知就不会出现,除非他们在设置中改回允许。即便后来点了允许,他们也可以禁用锁屏、通知中心、横幅、声音或徽章。Focus 模式和计划汇总也会隐藏或延迟提醒。
在 Android 上,要求取决于系统版本。较新的版本需要运行时通知权限,因此应用更新后可能会突然停止显示通知,直到用户再次批准。可见性还取决于通知渠道。如果渠道被静音或设置为低重要度,推送可能到达但不会打扰用户。
后台限制也会破坏预期。iOS 上的低电量模式和 Android 上的电池优化可能延迟后台工作、阻止后台数据或阻止应用处理仅数据消息。
要确认发生了什么,记录设备端看到的情况,而不仅仅是后端发送的内容:
- 应用内日志:“权限已授权”、“令牌已注册”、“收到通知”、“通知已展示”
- 系统指标:通知设置状态(启用/静默/渠道重要性)和电池模式
- 推送回调:应用在前台/后台是否收到消息
即便你的后端使用的是低代码工具,客户端日志仍然是区分“消息未收到”和“收到但被抑制”的关键。
逐步排查:丢失推送如何调试
当推送丢失时,把它当作一条链来处理:令牌、提供者、载荷和应用行为。症状在 iOS 与 Android 上可能相同,所以按顺序检查以下几点。
- 确认你发送的是当前令牌。 将服务器保存的令牌与应用最近上报的令牌比对。记录你最后一次收到每个令牌的时间。
- 在发送前验证载荷。 保持在平台限制内,使用必需字段并避免格式错误的 JSON。如果你发送仅数据消息,确认应用已实现处理逻辑。
- 检查提供者凭据与环境。 对于 APNs,确认 key/certificate、team、bundle ID,以及是否针对沙盒或生产。对于 FCM,确认使用的是正确的项目凭据。
然后缩小范围,看是消息内容还是设备/应用行为:
- 发送一个最小测试通知。 一个小的标题/正文有助确认传输是否可用。
- 验证应用端处理与前台行为。 许多“丢失”的推送其实被接收但没有展示。有些应用在前台有意抑制横幅。
- 一次只改变一个变量。 试另一台设备、不同系统版本、Wi-Fi vs 蜂窝网络以及不同用户账号。如果只有某个账号失败,通常指向陈旧令牌或服务端定位问题。
一个实用模式是:如果 iOS 用户反馈丢失但 Android 正常,先在 iOS 上发送一个最小可见提醒。如果该提醒能到达,就把注意力放在载荷结构和应用处理上;如果不能到达,就优先检查令牌和 APNs 凭据/环境。
导致静默失败的常见错误
大多数推送问题不是服务中断,而是应用期望与 APNs/FCM 接受规则或手机允许之间的小不匹配。
最常见的是发送到不再有效的令牌。令牌会在重装、恢复或刷新后变化。如果你的服务器一直使用旧值,推送就发不到任何地方。
另一个问题是把推送投递当作有保证的。尽力而为的投递意味着在设备离线或处于省电策略下,延迟或丢失消息是正常的。对于重要事件(订单更新、安全告警),你需要应用内的回退方案,比如在打开时拉取最新状态。
常见导致推送丢失的原因包括:
- iOS 或 Android 的陈旧令牌在重装/刷新后仍被保存
- 超出推送载荷限制(太多自定义数据、超大图片、超长字符串)
- 依赖后台投递做静默更新,结果被 OS 限流
- 混用 iOS 环境(开发 vs 生产),令牌与 APNs 端点不匹配
- 忽视用户选择退出、Focus/勿扰、被禁用的通知渠道(Android)或应用级通知权限
示例:一个零售应用发送了包含大量追踪历史的“订单已发货”提醒。发送调用看起来成功,但载荷被拒或被截断,用户什么也看不到。保持推送简短,并把详情放在 API 后端去拉取。
在责怪 APNs 或 FCM 之前的快速检查清单
在把问题归咎于提供者之前,先做个健康检查:
- 确认令牌对该用户和设备是正确的。 它应该存在、最近更新并映射到正确的会话。
- 验证提供者凭据现在仍有效。 检查 APNs key/cert 和 FCM 凭据是否对应正确的应用/项目。
- 验证载荷形状与大小。 保持在限制内并使用正确字段。
- 有意设置 TTL、优先级与合并。 短 TTL 会在设备上线前过期;低优先级会延迟投递;合并会替换早期消息。
- 区分“服务器接受”与“设备展示”。 对比服务器日志(请求/响应/消息 ID)与客户端日志(使用的令牌、处理器是否被调用)。
然后做一个快速的设备端检查:应用通知是否允许、频道/类别是否正确配置(Android 通知渠道是常见坑)、Focus/勿扰 是否生效,以及后台限制。
示例:诊断丢失的订单更新通知
一个客服在后台点“发送订单更新”,订单号 #1842。后端日志显示“通知已发送”,但客户既没在 iPhone 上看到,也没在 Android 上看到。
从后端开始排查。大多数“丢失”通知要么根本没被推送服务接受,要么被接受后因为设备无法(或不愿)显示而被丢弃。
先检查后端
查找一次可追踪的发送尝试(一次订单更新应产生一次推送请求)。然后验证:
- 使用的令牌是该用户和设备保存的最新令牌。
- 推送提供者的响应是成功,并且你保存了任何错误码。
- 载荷符合平台规则(大小限制、必需字段、有效 JSON)。
- 认证有效(APNs key/cert 与 team/bundle IDs,或 FCM 凭据)。
- 你定位的是正确的 iOS 环境(沙盒 vs 生产)。
如果日志显示拒绝如 “unregistered/invalid token”,就是令牌生命周期问题。如果提供者接受了消息但未到达设备,则关注载荷类型和操作系统行为。
在手机上进行检查
现在验证手机是否被允许显示该提醒:
- 应用的通知已启用(且允许锁屏/横幅显示)。
- Focus/勿扰 或 通知汇总未将其隐藏。
- 省电模式没有限制后台工作(Android 更常见)。
- 应用状态与消息类型一致(前台处理可能吞掉提醒)。
常见结果是:令牌没问题,但消息是仅数据(Android)或缺少 iOS 上后台处理的正确设置,因此系统根本不显示提醒。解决办法是发送与你期望效果相匹配的载荷类型(可见提醒 vs 后台更新),并保持令牌更新与提供者响应的清晰日志记录。
后续步骤:在产品中提升推送可靠性
推送通知看起来很简单,直到它们成为核心功能。可靠性来自你能控制的部分:令牌卫生、载荷规范以及回退路径。
为丢失做好准备。推送适合“立即查看”的时刻,但不应是关键事件的唯一路径。应用内收件箱可以让用户事后补看,电子邮件或短信可覆盖如密码重置或支付问题等高价值动作。
保持载荷精简。把推送载荷当作提示,而不是完整消息。发送事件类型和 ID,然后在应用打开或收到合适的后台更新时,从后端 API 拉取详情。
为你的团队写一份简短的运行手册,以便调试保持一致:包括同意状态、令牌新鲜度、提供者响应码、载荷大小/形状,以及环境/凭据。
如果你使用 AppMaster (appmaster.io) 构建,它可以方便地把令牌存储、审计日志和触发推送的业务逻辑集中在一个后端,同时仍能发布正确处理 APNs 与 FCM 的原生 iOS 与 Android 应用。
常见问题
APNs 是 Apple 提供的 iOS 推送传递服务,FCM 是 Google 提供的推送服务(也可以间接针对 iOS,通过 APNs)。你的应用仍然决定如何处理消息,但这些服务决定你如何认证、如何格式化载荷,以及可以期待的投递行为。
把令牌当作会变化的地址来处理。将它们与平台和环境信息一起存储,每当应用上报新值就更新它们,并在提供者提示无效时删除。实用规则是对令牌做“upsert”,并保留“最后一次看到”时间戳,以便快速发现陈旧记录。
iOS 上,令牌常在重装应用、恢复设备、某些系统更新或在开发时在沙盒与生产环境切换后发生变化。Android 上,FCM 令牌可能在重装、清除应用数据、设备恢复或 Google 旋转令牌时刷新。你的应用应监听刷新事件并立即把新令牌发送到后端。
保持推送载荷小,把它当作提示。发送简短的标题/正文(如果需要可见提醒)和一个稳定标识符,比如 order_id,然后让应用从你的 API 获取完整详情。这样可以避免载荷限制、减少奇怪的边缘情况,并让跨平台行为更一致。
通知载荷用于展示给用户,而纯数据载荷是留给应用处理的。在 Android 上,纯数据消息可能被后台限制或电池策略延迟或阻止,所以它们并不是触发即时工作的可靠方式。在 iOS 上,后台推送也不能保证立即运行你的代码,因此应把它们当作刷新提示,而不是实时任务触发器。
不,投递是尽力而为的。即便 APNs 或 FCM 接受了你的请求,设备也可能离线、消息因 TTL 过期、系统限流、或用户设置抑制了提醒。设计你的应用时要确保关键状态在用户打开应用时总是正确,即使通知从未显示过也能补救。
先把“已发送”和“已展示”分开确认。确认令牌是最新的,发送一个最小的标题/正文测试载荷,并验证你使用的是正确的 APNs/FCM 凭据(以及 iOS 的正确环境)。如果提供者接受了消息,再检查手机设置如 Focus/勿扰、应用通知权限和 Android 通知渠道,因为消息可能已被接收但被系统屏蔽。
在 iOS 上,大多数问题来自权限被拒绝、Focus 模式,或在开发时把令牌发送到错误的 APNs 环境(沙盒 vs 生产)。在 Android 上,常见阻塞是新系统版本的运行时通知权限、被静音或低重要度的通知渠道,以及激进的电池优化延迟后台处理。相同的后端发送在设备端可能因这些差异而被静默或延迟。
TTL 控制提供者在设备离线时应尝试投递多久,collapse(合并)设置决定新消息是否替换掉旧的待投递消息。短 TTL 会在设备离线较久时导致通知丢失,而合并键可以让用户只看到最新的更新。根据你想要的用户体验有意地设置这些值,而不是一直用默认值。
把令牌存储、定位规则和发送日志放在一起,这样你可以端到端追踪每次推送尝试。AppMaster 可以帮助集中令牌表、审计日志和推送触发的业务逻辑,同时你的原生 iOS 和 Android 应用仍然负责正确处理 APNs 与 FCM。关键是记录令牌更新、提供者响应和客户端接收情况,这样你就能确定故障是出在服务端、提供者还是设备行为。


