课前通知提醒实现
约 1466 字大约 5 分钟
2026-03-31
本文说明 SleepIn 当前版本的“上课提醒(课前通知)”实现,重点覆盖权限入口、调度链路、提醒判定规则,以及近期针对“延迟/打开应用后才补发”的修正方案。
1. 背景与目标
1.1 功能目标
- 用户在设置页开启“上课提醒”后,系统可以在上课前 N 分钟发送通知。
- 提醒分钟数沿用设置中的
reminderMinutes(例如 5/10/15/30 分钟)。 - 课程、作息表、当前激活课表变化后,提醒计算可以快速同步。
1.2 设计约束
- 保持现有手动 DI,不引入 Hilt。
- 复用现有“学期周次计算 + 周次可见性”业务规则,避免与首页/小组件出现不一致。
- Android 12+ 需关注“闹钟和提醒(精确闹钟)”能力;Android 13+ 需显式申请
POST_NOTIFICATIONS。 - 采用“AlarmManager 优先 + WorkManager 兜底”双通道,尽量严格命中上课前 N 分钟。
2. 代码位置
- 设置与权限入口
app/src/main/java/com/kurosu/sleepin/ui/screen/settings/SettingsScreen.ktapp/src/main/java/com/kurosu/sleepin/MainActivity.ktapp/src/main/AndroidManifest.xml
- 调度层
app/src/main/java/com/kurosu/sleepin/reminder/CourseReminderScheduler.kt
- 闹钟触发入口
app/src/main/java/com/kurosu/sleepin/reminder/CourseReminderAlarmReceiver.ktapp/src/main/java/com/kurosu/sleepin/reminder/CourseReminderRescheduleReceiver.kt
- 执行层(直达执行 + Worker 兜底)
app/src/main/java/com/kurosu/sleepin/reminder/CourseReminderNotificationExecutor.ktapp/src/main/java/com/kurosu/sleepin/reminder/CourseReminderWorker.kt
- 应用启动与设置联动
app/src/main/java/com/kurosu/sleepin/SleepInApplication.kt
- 复用的周次计算逻辑
app/src/main/java/com/kurosu/sleepin/ui/screen/home/HomeWeekDateCalculator.kt
3. 核心流程
flowchart TD
A[用户在设置开启上课提醒] --> B[DataStore: notificationsEnabled/reminderMinutes]
B --> C[SleepInApplication 监听设置变化]
C --> D[CourseReminderScheduler.syncPeriodic: 周期兜底]
C --> E[CourseReminderScheduler.scheduleNextExactAlarm: 安排最近一次精确闹钟]
E --> F[到点触发 CourseReminderAlarmReceiver]
F --> G[CourseReminderNotificationExecutor.execute 直达判定并通知]
G --> H[成功: 重新计算并安排下一次精确闹钟]
G --> I[失败: requestImmediateCheck(expedited=true)]
I --> J[CourseReminderWorker.doWork 兜底执行]4. 关键实现细节
4.1 调度策略
CourseReminderScheduler 维护两条通道:
- 精确闹钟通道:使用
AlarmManager仅安排“最近一条即将到来的提醒点”。 - 周期兜底通道:
WorkManager每 15 分钟检查一次,用于补偿系统限制导致的漏触发。
SleepInApplication 目前的行为:
- 监听
notificationsEnabled/reminderMinutes,变更后同步周期任务并重排下一次精确闹钟。 - Room 表变更(课程、课表、作息)时重排下一次精确闹钟。
- 不再在这些场景强制
requestImmediateCheck(),以减少“打开应用后立刻补发”的副作用。
CourseReminderRescheduleReceiver 在以下事件发生后仅重建下一次闹钟:
BOOT_COMPLETEDTIME_CHANGEDTIMEZONE_CHANGEDMY_PACKAGE_REPLACED
4.2 提醒判定规则
CourseReminderNotificationExecutor(被闹钟接收器和 Worker 复用)在每次执行时:
- 读取设置,若
notificationsEnabled=false则直接结束。 - 读取当前激活课表,并通过
calculateSemesterWeekInfo(...)判断学期是否进行中。 - 读取课程与作息节次时间,按
WeekType规则筛选“当前周有效课程”。 - 仅处理“今天”的课程,计算:
startAt:课程开始时间。reminderAt = startAt - reminderMinutes。
- 若当前时间落入窗口则发送提醒:
- 闹钟直达执行窗口:
reminderAt <= now <= min(startAt, reminderAt + 2分钟) - 周期兜底执行窗口:
reminderAt <= now <= min(startAt, reminderAt + 15分钟)
- 闹钟直达执行窗口:
这样设计的原因是:闹钟路径追求严格准点,兜底路径接受更宽容忍区间以提高到达率。
4.3 去重策略
为避免同一节课在短时间内重复通知,执行器将已通知 key 写入本地偏好:
- key 由
timetableId/courseId/sessionId/startAtEpochMillis组成。 - 仅保留最近 48 小时 key,用于控制数据规模。
4.4 权限与设置页联动
- Manifest 声明:
android.permission.POST_NOTIFICATIONSandroid.permission.SCHEDULE_EXACT_ALARMandroid.permission.RECEIVE_BOOT_COMPLETED
- 设置页(
SettingsScreen)显示提醒相关能力状态并支持整行点击跳转:- 精确闹钟授权(已授权/未授权)
- 通知权限(已开启/未开启)
- 电池优化白名单(已加入/未加入)
- 后台运行限制(正常/受限)
- 精确闹钟授权引导使用
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM。 - 若 ROM 无法直达目标页面,按“目标页 -> 应用详情页 -> 系统设置页”顺序降级。
MainActivity与执行器会继续做通知权限兜底,避免后台抛异常。
4.5 当前状态说明
- “启用灵动岛通知”在设置页已标记为“未实现”,当前不会参与提醒触发链路。
5. 调试与排障建议
5.1 看不到精确闹钟授权页/跳转异常
优先检查:
- 设备是否 Android 12+(
SCHEDULE_EXACT_ALARM仅 S+ 生效)。 - 是否已经处于已授权状态(已授权时系统通常不再弹出流程页)。
- 厂商 ROM 可能不支持某些 intent,查看是否已回退到应用详情页。
- 若日志出现
Error getting package info: com.kurosu.sleepin,通常是 ROM 拒绝了包名定向页,可走通用设置入口手动配置。
5.2 开启提醒但通知延迟或要打开 App 才出现
按顺序排查:
- 查看精确闹钟能力:
adb shell cmd appops get com.kurosu.sleepin SCHEDULE_EXACT_ALARM。 - 查看闹钟登记情况:
adb shell dumpsys alarm | findstr /i sleepin。 - 查看 WorkManager 作业排队:
adb shell dumpsys jobscheduler com.kurosu.sleepin。 - 查看设备是否频繁进入 idle:
adb shell dumpsys deviceidle。 - 结合业务条件再确认:学期是否进行中、当天是否有当前周可见课程、是否已命中过去窗口或被去重。
经验:即使精确闹钟权限已开,部分机型仍可能出现系统延迟。当前实现通过“闹钟直达 + 周期兜底”提升稳定性,但不能在所有 ROM 上保证绝对零延迟。
5.3 开发期验证建议
- 将提醒分钟数设置为 5 分钟。
- 创建“几分钟后开始”的课程样本(确保周次可见)。
- 确认设置页四个状态项均可用(至少精确闹钟和通知权限要可用)。
- 熄屏等待提醒点,观察是否在预期时间附近通知。
- 切换激活课表或修改课程后,确认“下一次闹钟时间”按新课表重排。
6. 关联阅读
- 更新检测任务链路:
docs/SleepIn-Docs/docs/dev/4.feature/1.update-check.md - Widget 与 WorkManager:
docs/SleepIn-Docs/docs/dev/3.business/5.widget-workmanager.md - 常见排障:
docs/SleepIn-Docs/docs/dev/5.others/1.troubleshooting.md
更新日志
2026/5/18 12:59
查看所有更新日志
2a526-docs(readme): update screenshot layout于
