提醒与推送功能实现说明
1. 功能概述
客户端中的提醒功能通常可以分为两类:
本地提醒
远程推送
两者的区别在于提醒由谁触发。
无论使用哪种方式,一般都不需要让客户端长期常驻后台。
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
推送可能遗漏的消息
→ 客户端启动后向服务端同步
完整业务数据
→ 保存在业务服务端
提醒与推送功能实现说明
1. 功能概述
客户端中的提醒功能通常可以分为两类:
本地提醒
远程推送
两者的区别在于提醒由谁触发。
本地提醒由手机客户端提前注册给操作系统,到达指定时间后由操作系统触发。
远程推送由服务端产生消息,再通过系统或手机厂商的推送服务发送到用户设备。
无论使用哪种方式,一般都不需要让客户端长期常驻后台。
2. 本地提醒
本地提醒适用于提醒时间已经确定的场景,例如:
指定时间提醒
倒计时结束提醒
每日重复提醒
每周重复提醒
日程开始前提醒
基本流程如下:
本地提醒的触发者是手机操作系统,而不是客户端进程。
因此,不应该通过客户端中的普通定时器持续检查时间:
因为客户端进入后台后,进程可能被系统暂停或回收,普通定时器无法保证正常执行。
正确方式是将提醒时间注册给操作系统。
不同平台可以使用对应的系统能力:
iOS:本地通知
Android:系统闹钟或系统通知调度能力
客户端主要负责:
注册提醒
更新提醒
取消提醒
处理通知点击事件
在必要时恢复已经注册的提醒
3. 远程推送
远程推送适用于提醒时间或提醒内容由服务端决定的场景,例如:
系统主动发送通知
其他用户发送消息
任务状态发生变化
后端检测到某个事件
服务端生成新的提醒
多设备之间同步状态
远程推送的基本流程如下:
服务端通常不能直接连接用户手机。
原因包括:
手机通常没有固定公网地址
手机可能位于运营商网络或 NAT 后
客户端进程可能已经被关闭
手机可能处于锁屏或省电状态
操作系统会限制普通应用的后台运行
因此,需要通过系统或手机厂商提供的推送通道发送消息。
4. 推送通道
不同设备使用的推送通道可能不同。
完整链路可以表示为:
手机系统与厂商推送服务器之间会维护系统级通信通道。
客户端不需要为了接收推送而一直在后台运行。
5. 设备标识注册
远程推送需要通过设备推送标识确定消息要发送到哪台设备。
客户端初始化推送服务后,会获得一个设备标识,例如:
Device Token
Registration Token
Client ID
CID
基本注册流程如下:
例如:
服务端发送消息时,根据用户找到对应的设备推送标识,再调用推送平台。
一个用户可能同时登录多台设备,因此通常是:
设备推送标识可能发生变化,客户端获得新的标识后需要重新上传到服务端。
6. 聚合推送平台
如果应用需要同时支持多种手机品牌,可以使用聚合推送平台,例如:
个推
极光推送
腾讯云移动推送
不使用聚合平台时,服务端可能需要分别调用不同通道:
接入聚合推送平台后,可以使用统一接口:
聚合平台主要提供:
统一客户端 SDK
统一服务端推送 API
多厂商通道适配
设备管理
推送结果统计
失败原因查询
通道选择和降级
聚合平台并不是绕过手机厂商,而是对不同厂商推送通道进行统一封装。
通常仍需要在各个厂商开发者平台创建应用,并将厂商提供的参数配置到聚合推送平台。
7. 服务端推送流程
服务端产生需要通知用户的业务事件后,可以执行以下流程:
伪代码如下:
服务端可以将具体推送平台封装成统一接口:
具体实现可以是:
这样业务逻辑不需要直接依赖某一个具体的推送平台。
8. 推送消息与业务数据
推送消息不应该作为业务数据的唯一来源。
更合理的方式是:
推送内容中只携带必要的跳转信息,例如:
客户端点击通知后:
这样设计的原因是推送存在以下情况:
消息可能延迟
消息可能发送失败
消息可能重复到达
消息可能被系统合并
用户可能关闭通知权限
设备可能长时间离线
消息到达时业务状态可能已经发生变化
因此:
9. 前台消息与后台推送
远程推送主要解决客户端处于后台或已经关闭时的消息通知问题。
当客户端处于前台时,可以使用 WebSocket 接收实时消息。
客户端处于后台或已经关闭时:
因此可以组合使用:
需要注意,WebSocket 不能代替系统推送。
客户端进入后台后,WebSocket 连接可能被系统暂停或断开,因此后台提醒仍然需要依赖系统推送通道。
10. 消息同步
推送不能保证所有消息一定到达,因此客户端打开应用或恢复前台时,通常还需要向服务端同步消息。
可以使用两种同步方式。
全量同步
客户端请求当前所有未读消息:
增量同步
客户端携带上一次同步位置:
服务端返回上次同步之后产生的消息。
这种方式可以补偿以下情况:
推送没有到达
用户关闭了通知权限
手机长时间离线
客户端被卸载后重新安装
推送只到达了用户的部分设备
11. 本地提醒与远程推送的区别
12. 推荐的整体结构
整体实现可以归纳为: