HarmonyOS 消息推送闭环实战:订阅、分层、限频与退订

推送最容易被做成“服务端发一条,客户端弹一下”。短期看能触达用户,长期看会带来反效果:用户不知道为什么收到消息、营销和服务通知混在一起、同一活动一天推好几次、退订后仍收到提醒。推送不是发送能力,而是一套用户沟通闭环。

本文从 HarmonyOS 应用侧整理推送治理方法:订阅场景、消息分层、Token 状态、限频去重、点击追踪和退订生效。具体推送服务可以接入 Push Kit 或团队已有服务,工程结构保持一致。

请添加图片描述

1. 推送先区分消息类型

类型示例处理原则
服务通知订单状态、审核结果与用户行为直接相关
功能提醒预约开始、任务到期用户主动订阅或触发
运营消息活动、内容推荐必须可退订和限频
系统消息安全提醒、账号异常文案克制,避免营销化

如果所有消息都走同一个通道,后续就很难解释订阅来源和退订规则。

请添加图片描述

2. 推送资料边界和模块落点

资料或位置作用
华为开发者文档中心:https://developer.huawei.com/consumer/cn/doc/查询 Push Kit、通知、应用服务能力入口
HarmonyOS 指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/查询通知、权限、后台和应用生命周期资料
services/push/PushScene.ets定义消息场景和频率
services/push/PushTokenStore.ets维护设备 token 状态
services/push/MessageRouter.ets处理到达、点击和跳转
services/push/PushAudit.ets记录发送结果和用户动作

推送涉及用户体验和合规说明,建议和隐私政策、权限说明、运营策略一起维护。

工程上可以把推送拆成几个稳定模块:

项目位置建议职责
services/push/PushScene.ets定义消息类型、订阅理由和限频
services/push/PushTokenStore.ets维护用户、设备和 token 关系
services/push/PushFrequencyGuard.ets控制重复消息和每日上限
services/push/MessageRouter.ets校验 payload 并完成安全跳转
services/push/PushAudit.ets记录到达、点击、退订等动作

这样推送问题出现时,能分清是 token 失效、频率配置错误、payload 错误,还是用户已退订。

3. PushScene 定义订阅场景

type PushCategory = 'service' | 'reminder' | 'marketing' | 'security';

interface PushScene {
  sceneId: string;
  category: PushCategory;
  title: string;
  userReason: string;
  maxPerDay: number;
  canUnsubscribe: boolean;
}

const pushScenes: PushScene[] = [
  {
    sceneId: 'order_status',
    category: 'service',
    title: '订单状态通知',
    userReason: '用于提醒订单处理进度',
    maxPerDay: 6,
    canUnsubscribe: false,
  },
  {
    sceneId: 'activity_recommend',
    category: 'marketing',
    title: '活动推荐',
    userReason: '用于推荐用户可能感兴趣的活动',
    maxPerDay: 1,
    canUnsubscribe: true,
  },
];

场景定义让推送可解释。服务通知和营销消息不应该共享同一套频率和退订策略。

4. Token 状态要可追踪

interface PushTokenState {
  userId: string;
  deviceId: string;
  token: string;
  valid: boolean;
  updatedAt: number;
}

class PushTokenStore {
  private readonly tokens = new Map<string, PushTokenState>();

  update(state: PushTokenState): void {
    this.tokens.set(`${state.userId}:${state.deviceId}`, state);
  }

  invalidate(userId: string, deviceId: string): void {
    const key = `${userId}:${deviceId}`;
    const old = this.tokens.get(key);
    if (old) {
      this.tokens.set(key, { ...old, valid: false, updatedAt: Date.now() });
    }
  }
}

Token 不是拿到后就不管。退出登录、注销账号、切换设备、系统权限变化时,都要同步状态。

5. 限频和去重必须在发送前判断

interface PushSendRecord {
  userId: string;
  sceneId: string;
  messageId: string;
  sentAt: number;
}

class PushFrequencyGuard {
  private readonly records: PushSendRecord[] = [];

  canSend(scene: PushScene, userId: string): boolean {
    const today = new Date().toDateString();
    const count = this.records.filter((item) =>
      item.userId === userId &&
      item.sceneId === scene.sceneId &&
      new Date(item.sentAt).toDateString() === today
    ).length;
    return count < scene.maxPerDay;
  }

  mark(record: PushSendRecord): void {
    this.records.push(record);
  }
}

限频不是运营后台口头约束,应用侧也应有保护。尤其是本地提醒或端侧触发消息,更要避免重复打扰。

请添加图片描述

6. MessageRouter 处理点击和跳转

interface PushPayload {
  messageId: string;
  sceneId: string;
  targetPage: string;
  params: Record<string, string>;
}

class MessageRouter {
  route(payload: PushPayload): string {
    if (!payload.targetPage) {
      return 'home';
    }
    if (payload.sceneId === 'order_status' && payload.params['orderId']) {
      return `order/detail?id=${payload.params['orderId']}`;
    }
    return payload.targetPage;
  }
}

点击跳转要校验参数,不能把远程 payload 原样作为路由执行。这样可以避免坏消息导致页面异常。

7. 退订记录要立即生效

class PushSubscriptionStore {
  private readonly unsubscribed = new Set<string>();

  unsubscribe(userId: string, sceneId: string): void {
    this.unsubscribed.add(`${userId}:${sceneId}`);
  }

  subscribed(userId: string, sceneId: string): boolean {
    return !this.unsubscribed.has(`${userId}:${sceneId}`);
  }
}

营销和提醒类消息必须尊重用户退订。退订后仍继续发送,是推送体验里最伤信任的问题之一。

8. 推送上线前的验证动作

场景操作预期结果
未订阅营销发送活动消息不展示
服务通知订单状态变化可正常提醒
超过限频连续发送两条活动消息第二条被拦截
Token 失效用户退出登录不再给旧设备发送
点击跳转payload 缺少参数回到安全页面

推送验证不要只看“手机能收到”。还要看频率、退订、点击、Token 失效和异常 payload。

9. 消息触达问题排查表

现象优先检查修复建议
用户说被打扰maxPerDay 和退订状态给运营消息限频
退出后仍收到Token 是否失效登出时更新 token 状态
点击打开错误页payload 参数是否校验通过 MessageRouter 统一跳转
服务通知收不到场景是否误判为营销分清 category
退订无效是否只改本地状态同步服务端订阅状态

推送上线前建议做一轮“反向验收”:用户退订后发送营销消息、Token 失效后发送服务通知、payload 缺少目标参数后点击通知。只有这些失败场景都能安全处理,推送才不会变成用户打扰源。

interface PushReleaseCheck {
  sceneRegistered: boolean;
  frequencyVerified: boolean;
  unsubscribeWorks: boolean;
  tokenInvalidationWorks: boolean;
  clickRouteSafe: boolean;
}

const pushReleaseCheck: PushReleaseCheck = {
  sceneRegistered: true,
  frequencyVerified: true,
  unsubscribeWorks: true,
  tokenInvalidationWorks: true,
  clickRouteSafe: true,
};

这份检查表可以和运营活动一起走。每次新增消息类型,都应该先确认它属于哪一类、是否可退订、频率是多少、点击后去哪里。

推送专项证据包:触达、限频和退订要一起验收

推送不是发出去就结束。读者真正关心的是:用户是否订阅、是否命中分层、是否超过频率、是否能退订、失败后是否有补偿。缺少这些证据,推送文章就会像运营配置说明。

证据项说明
subscribeState是否允许接收
segment命中的用户分层
dailyCount当日触达次数
unsubscribeAt退订时间
interface PushEvidence {
  userId: string
  subscribeState: 'on' | 'off'
  segment: string
  dailyCount: number
}

function canSendPush(e: PushEvidence): boolean {
  if (e.subscribeState !== 'on') return false
  if (e.dailyCount >= 3) return false
  return e.segment.length > 0
}

这段代码把推送合规和体验边界放到发送前,避免只在发送失败后补救。

推送触达复现场景:给读者一组可执行核验

推送治理必须验证退订和限频。否则文章只证明消息能发出去,没有证明不会打扰用户。

核验维度读者需要准备的证据
输入页面入口、用户动作、关键参数
过程日志、状态变化、异常分支
输出UI 表现、回调结果、持久化结果
回归同场景重复执行后的结果
interface PushReplayCase {
  userId: any
  scene: any
  dailyCount: any
  unsubscribeState: any
}

const replay68: PushReplayCase = {
  userId: 'sample',
  scene: 'sample',
  dailyCount: 'sample',
  unsubscribeState: 'sample',
}

function assertReplay68(item: PushReplayCase): void {
  if (item.dailyCount > 3) throw new Error('单日推送次数超过限制')
}

这组核验把推送发送前的用户状态、分层和频次合并判断,避免触达能力变成打扰来源。

推送退订回放表:把文章方法变成可复现动作

消息推送的关键不是送达率,而是用户能否控制打扰。建议用一个已退订用户、一个高频触达用户、一个新订阅用户分别回放发送链路,确认每类用户的处理不同。

回放动作核验方式
已退订用户不发送准备输入、执行操作、记录结果、给出结论
高频用户被限流准备输入、执行操作、记录结果、给出结论
新订阅用户正常接收准备输入、执行操作、记录结果、给出结论
失败回执进入补偿队列准备输入、执行操作、记录结果、给出结论

推送回放时不要只看设备是否收到通知,还要看用户是否应该收到。读者可以把订阅状态、用户分层、当天已发送次数和失败回执放到同一条记录里:订阅关闭时不发送,限频命中时不发送,服务端失败时进入补偿。这样写出来的推送链路才是完整的触达治理,而不是单纯证明消息通道可用。

推送触达的落地边界:不要把边界留给读者猜

推送链路要区分系统通知、运营触达和业务提醒。业务提醒通常要求及时,运营触达更强调限频,系统通知更强调可解释和可退订。读者实现时不要把三类消息放进同一个发送函数,否则后续限频、退订和投诉处理都会混在一起。

落地项处理要求
业务提醒优先保证时效需要有明确输入、处理边界和失败兜底
运营触达优先尊重频次需要有明确输入、处理边界和失败兜底
系统通知必须说明来源需要有明确输入、处理边界和失败兜底
退订状态优先级最高需要有明确输入、处理边界和失败兜底

这类边界写清楚后,读者不需要猜哪些逻辑属于页面、哪些属于服务、哪些属于发布前验收。文章的价值也会从“讲了一个功能”变成“给了一套可迁移的工程判断”。

10. 小结:推送要尊重用户节奏

HarmonyOS 消息推送要做成闭环,而不是只看发送成功率。订阅说明让用户知道为什么收到,消息分层避免服务通知和营销混杂,限频保护用户体验,退订体现尊重,点击追踪帮助分析效果。这样的推送能力才适合长期运营。

Logo

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

更多推荐