Skip to content

提醒与推送功能实现说明 #57

Description

@znnnnnnn-wil

提醒与推送功能实现说明

1. 功能概述

客户端中的提醒功能通常可以分为两类:

  1. 本地提醒

  2. 远程推送

两者的区别在于提醒由谁触发。

  • 本地提醒由手机客户端提前注册给操作系统,到达指定时间后由操作系统触发。

  • 远程推送由服务端产生消息,再通过系统或手机厂商的推送服务发送到用户设备。

无论使用哪种方式,一般都不需要让客户端长期常驻后台。


2. 本地提醒

本地提醒适用于提醒时间已经确定的场景,例如:

  • 指定时间提醒

  • 倒计时结束提醒

  • 每日重复提醒

  • 每周重复提醒

  • 日程开始前提醒

基本流程如下:

用户设置提醒
    ↓
客户端保存提醒信息
    ↓
客户端向手机操作系统注册本地通知
    ↓
客户端退出或进程被回收
    ↓
到达提醒时间
    ↓
操作系统展示通知

本地提醒的触发者是手机操作系统,而不是客户端进程。

因此,不应该通过客户端中的普通定时器持续检查时间:

客户端持续运行
    ↓
每隔一段时间检查是否到达提醒时间

因为客户端进入后台后,进程可能被系统暂停或回收,普通定时器无法保证正常执行。

正确方式是将提醒时间注册给操作系统。

不同平台可以使用对应的系统能力:

  • iOS:本地通知

  • Android:系统闹钟或系统通知调度能力

客户端主要负责:

  • 注册提醒

  • 更新提醒

  • 取消提醒

  • 处理通知点击事件

  • 在必要时恢复已经注册的提醒


3. 远程推送

远程推送适用于提醒时间或提醒内容由服务端决定的场景,例如:

  • 系统主动发送通知

  • 其他用户发送消息

  • 任务状态发生变化

  • 后端检测到某个事件

  • 服务端生成新的提醒

  • 多设备之间同步状态

远程推送的基本流程如下:

服务端产生消息
    ↓
服务端调用推送服务
    ↓
推送服务将消息发送到手机系统
    ↓
手机系统展示通知

服务端通常不能直接连接用户手机。

原因包括:

  • 手机通常没有固定公网地址

  • 手机可能位于运营商网络或 NAT 后

  • 客户端进程可能已经被关闭

  • 手机可能处于锁屏或省电状态

  • 操作系统会限制普通应用的后台运行

因此,需要通过系统或手机厂商提供的推送通道发送消息。


4. 推送通道

不同设备使用的推送通道可能不同。

iPhone
    ↓
Apple APNs

支持 Google 服务的 Android

Firebase Cloud Messaging

中国大陆 Android

华为、小米、OPPO、vivo、荣耀等厂商推送通道

完整链路可以表示为:

业务服务端

Apple、Google 或手机厂商推送服务

手机操作系统

用户设备上的通知栏

手机系统与厂商推送服务器之间会维护系统级通信通道。

客户端不需要为了接收推送而一直在后台运行。


5. 设备标识注册

远程推送需要通过设备推送标识确定消息要发送到哪台设备。

客户端初始化推送服务后,会获得一个设备标识,例如:

  • Device Token

  • Registration Token

  • Client ID

  • CID

基本注册流程如下:

客户端初始化推送 SDK

向推送平台注册设备

获得设备推送标识

客户端将设备标识上传到业务服务端

业务服务端将用户与设备标识关联

例如:

{
"userId": "user-1001",
"deviceId": "device-001",
"platform": "android",
"pushProvider": "getui",
"pushToken": "push-token-value"
}

服务端发送消息时,根据用户找到对应的设备推送标识,再调用推送平台。

用户 ID

查询用户绑定的设备

获得设备推送标识

调用推送服务

一个用户可能同时登录多台设备,因此通常是:

一个用户

多个设备

多个推送标识

设备推送标识可能发生变化,客户端获得新的标识后需要重新上传到服务端。


6. 聚合推送平台

如果应用需要同时支持多种手机品牌,可以使用聚合推送平台,例如:

  • 个推

  • 极光推送

  • 腾讯云移动推送

不使用聚合平台时,服务端可能需要分别调用不同通道:

业务服务端
├── 调用 APNs
├── 调用华为推送
├── 调用小米推送
├── 调用 OPPO 推送
├── 调用 vivo 推送
└── 调用其他厂商推送

接入聚合推送平台后,可以使用统一接口:

业务服务端

聚合推送平台

根据设备选择推送通道
├── APNs
├── FCM
├── 华为推送
├── 小米推送
├── OPPO 推送
└── vivo 推送

聚合平台主要提供:

  • 统一客户端 SDK

  • 统一服务端推送 API

  • 多厂商通道适配

  • 设备管理

  • 推送结果统计

  • 失败原因查询

  • 通道选择和降级

聚合平台并不是绕过手机厂商,而是对不同厂商推送通道进行统一封装。

通常仍需要在各个厂商开发者平台创建应用,并将厂商提供的参数配置到聚合推送平台。


7. 服务端推送流程

服务端产生需要通知用户的业务事件后,可以执行以下流程:

业务事件发生

保存通知数据

查询用户绑定的设备

调用推送平台

推送平台将消息发送到设备

伪代码如下:

public void pushToUser(Long userId, Notification notification) {
// 保存通知数据
notificationRepository.save(notification);

// 查询用户绑定的设备
List<UserDevice> devices =
    userDeviceRepository.findByUserId(userId);

// 向用户的所有设备发送推送
for (UserDevice device : devices) {
    pushService.send(
        device.getPushToken(),
        notification.getTitle(),
        notification.getContent(),
        notification.getTargetId()
    );
}

}

服务端可以将具体推送平台封装成统一接口:

public interface PushService {
PushResult send(PushRequest request);
}

具体实现可以是:

GetuiPushService
JPushService
TpnsPushService
ApnsPushService
FcmPushService

这样业务逻辑不需要直接依赖某一个具体的推送平台。


8. 推送消息与业务数据

推送消息不应该作为业务数据的唯一来源。

更合理的方式是:

服务端保存完整业务数据

向设备发送推送通知

用户点击通知

客户端根据业务 ID 请求服务端

获取最新业务数据

推送内容中只携带必要的跳转信息,例如:

{
"notificationId": "notification-001",
"type": "TASK_REMINDER",
"title": "任务提醒",
"content": "你有一个任务即将开始",
"targetId": "task-1001"
}

客户端点击通知后:

读取通知中的 type 和 targetId

打开对应页面

向服务端请求最新数据

这样设计的原因是推送存在以下情况:

  • 消息可能延迟

  • 消息可能发送失败

  • 消息可能重复到达

  • 消息可能被系统合并

  • 用户可能关闭通知权限

  • 设备可能长时间离线

  • 消息到达时业务状态可能已经发生变化

因此:

服务端数据库中的数据是最终业务状态,推送只是通知用户有新事件发生。


9. 前台消息与后台推送

远程推送主要解决客户端处于后台或已经关闭时的消息通知问题。

当客户端处于前台时,可以使用 WebSocket 接收实时消息。

客户端处于前台

WebSocket 接收消息

展示应用内通知、消息列表或红点

客户端处于后台或已经关闭时:

服务端

远程推送

系统通知栏

因此可以组合使用:

前台实时通信:WebSocket

后台消息提醒:远程推送

固定时间提醒:本地通知

需要注意,WebSocket 不能代替系统推送。

客户端进入后台后,WebSocket 连接可能被系统暂停或断开,因此后台提醒仍然需要依赖系统推送通道。


10. 消息同步

推送不能保证所有消息一定到达,因此客户端打开应用或恢复前台时,通常还需要向服务端同步消息。

客户端启动或恢复前台

请求服务端获取最新消息

同步未读消息和业务状态

更新客户端界面

可以使用两种同步方式。

全量同步

客户端请求当前所有未读消息:

GET /notifications?status=unread

增量同步

客户端携带上一次同步位置:

GET /notifications/sync?after=last-sync-time

服务端返回上次同步之后产生的消息。

这种方式可以补偿以下情况:

  • 推送没有到达

  • 用户关闭了通知权限

  • 手机长时间离线

  • 客户端被卸载后重新安装

  • 推送只到达了用户的部分设备


11. 本地提醒与远程推送的区别

对比项 本地提醒 远程推送
触发方 手机操作系统 业务服务端
是否需要网络 注册后通常不需要 通常需要
时间是否提前确定 不一定
是否需要业务后端 不一定 需要
客户端是否需要常驻 不需要 不需要
适用场景 固定时间提醒 动态业务消息
主要依赖 系统通知调度 推送平台和厂商通道

12. 推荐的整体结构

                       ┌──────────────────┐
                       │     业务服务端     │
                       │ 保存消息和业务状态 │
                       └─────────┬────────┘
                                 │
                  ┌──────────────┴──────────────┐
                  │                             │
                  ▼                             ▼
              WebSocket                    推送平台
            前台实时消息                        │
                                                ▼
                                  APNs / FCM / 厂商推送
                                                │
                                                ▼
                                            系统通知

客户端创建固定时间提醒

注册本地通知

操作系统到达指定时间后触发

整体实现可以归纳为:

固定时间提醒
→ 使用本地通知

服务端主动产生的消息
→ 使用远程推送

客户端前台实时消息
→ 使用 WebSocket

推送可能遗漏的消息
→ 客户端启动后向服务端同步

完整业务数据
→ 保存在业务服务端

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions