推送功能上线,服务端日志显示消息全部发送成功,用户端投诉“从来没收到过”。排查发现两种情况混在一起:一部分用户的 Token 在重装应用之后已经失效,服务端还拿着旧 Token 在发;另一部分用户确实在系统设置里关掉了通知权限,但服务端根据“Token 存在”就判定“可触达”,继续推送。Push Kit 的“发送成功”只代表消息交给了推送通道,不代表用户设备上一定展示。把 Token 当成“设备可达”的唯一依据,线上一定会出问题。

Token 要回传、要去重、要感知失效

客户端拿到 Push Token 之后,第一件事是立刻回传服务端,不要等下一次启动。重装应用、清理数据、恢复出厂设置都会让旧 Token 失效,服务端如果保存了多个历史 Token 却没有去重,消息可能发往一个已经不存在的设备标识。更隐蔽的情况是用户关闭了通知权限,Token 还在,推送通道返回成功,但系统侧不会展示通知。客户端需要监听通知授权状态的变化,在权限关闭时告诉服务端“这个设备暂时不可触达”,而不是让服务端靠猜。

typescript

import { notificationManager } from '@kit.NotificationKit';

async function checkNotificationEnabled(): Promise<boolean> {
  try {
    return await notificationManager.isNotificationEnabled();
  } catch {
    return false;
  }
}

async function syncTokenState(token: string): Promise<void> {
  const enabled = await checkNotificationEnabled();
  await fetch('https://api.example.com/push/state', {
    method: 'POST',
    body: JSON.stringify({ token, enabled }),
  });
}

isNotificationEnabled 的结果要和 Token 一起上报。服务端只对 enabled: true 的设备做“可触达”标记,false 的设备走静默或站内信兜底,不要继续发系统通知。

通道设计在前,不要所有消息塞一个 Slot

系统通知、交易提醒、互动消息、活动推送的优先级和可关闭策略完全不同。把所有消息塞进同一个 Notification Slot,用户关掉一个就等于关掉全部,体验极差。按业务类型拆 Slot:交易类走 SERVICE_INFORMATION,互动类走 SOCIAL_COMMUNICATION,营销类走低级别通道。Slot 的级别和类型在创建时定住,后续消息按类型落槽,用户可以对每一类单独开关。CONTENT_INFORMATION 默认级别较低,容易被静默,业务提醒不要往这个槽里塞。

同一 ID 覆盖是特性,但终态不能被迟到事件逆转

进度类通知用同一个 id 更新,系统会覆盖旧通知,这是设计好的行为。坑在于任务已经完成或取消之后,一个迟到的“运行中”事件如果仍然带着同一个 id 发布,会把终态通知重新拉回“进行中”。通知的发布资格要加业务状态约束:终态之后拒绝任何进度更新,除非是新任务、新 epoch。状态机只允许 IDLE → RUNNING → DONE/FAILED/CANCELLED,终态不可逆。

推送链路的可靠性不在“能收到”,在 Token 状态、通道语义、终态约束三件事全部闭环。Token 要带着权限状态回传,通道按业务类型拆分,终态通知不接受迟到更新。三处收住,服务端的“发送成功”才和用户的“看到了”对得上。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐