1898 words
9 minutes
MiniAgent 的 Android 提醒:从 FCM 到系统日历与本地闹钟

最开始的问题很普通:在 APK 里设了待办和定时任务,到点后手机没有任何提醒。

任务其实已经保存到 mini-agent 了。以前的提醒出口是 Telegram;APK 只是一个能查看和创建任务的客户端。把“保存任务”和“在手机上提醒”混在一起想,方案会越做越重。后面这轮折腾,主要是在把两件事拆开。

一开始想走 FCM#

第一个版本是标准的 FCM 路线。

  • APK 接入 Firebase Messaging,登录后上报 FCM token;
  • 后端保存 token;
  • 定时任务触发时,后端用 Firebase Admin SDK 调 FCM HTTP v1;
  • APK 收到消息后用 NotificationCompat 展示系统通知。

这套东西在有 Google Play 服务、服务端能访问 Google 的环境里没有什么特别之处。Android 端还需要 POST_NOTIFICATIONS,Firebase 项目要有 google-services.json,服务端则要保存 Admin service account。私钥没有进 APK,也没有提交到 Git。

问题不在代码。

国内设备不一定有 GMS,即使有,FCM 也不稳定。更直接的一次检查是在阿里云 ECS 上:oauth2.googleapis.com:443fcm.googleapis.com:443 都能解析 DNS,但 HTTPS 连接超时。没有 OAuth access token,FCM HTTP v1 根本发不出去。

所以 FCM 被保留为 Google 生态下的可选通道,但不能承担这台个人设备的提醒主链路。

厂商推送看起来对,实际卡在资质#

接着调研了小米、华为、OPPO、vivo 等厂商通道,以及个推、极光这类聚合推送服务。

这确实是国内 App 的常规做法:厂商系统进程维持长连接,应用进程被杀掉后仍可能收到通知。定时提醒应该按“服务与通讯”“私信”“系统消息”之类的类别发送,不能落到营销通道。

但个人项目碰到了另一个边界。以小米通道为例,实际接入和消息类别申请需要企业开发者资质。为了一个个人 mini-agent 去接多厂商 SDK、控制台和合规披露,投入明显不成比例。

这条路没有继续推进。不是厂商通道不好,而是它不适合当前的约束。

也试过让本机替云端发 FCM#

既然 ECS 到不了 Google,曾经考虑过另一条链:APK 把任务和 token 放到云端,本机 mini-agent 同步任务,到点后本机持有 Firebase Admin 凭据并发送 FCM。

它能绕过 ECS 的网络限制,但立刻带来一个新问题:本机什么时候能拿到刚在 APK 创建的任务?每日同步显然不够。长轮询、变更队列、版本游标、断线补偿都可以做,只是为了通知又引入了一条常驻同步链路。

而且本机必须开着。它从“推送服务端”变成了家里的提醒网关,个人使用可以接受,架构上却不够干净。

这次探索留下的结论是:任务数据可以同步,提醒不必一定通过远程推送完成。

最后把待办和定时拆开#

最终方案没有继续追求“一种通知渠道解决所有问题”。待办和定时的语义不同,落到 Android 上也不该用同一种组件。

待办:写入系统日历#

待办有截止时间时,APK 在云端创建成功后写入 CalendarContract

  • 创建一个系统日历事件;
  • 添加提醒;
  • 保存 mini-agent 任务与日历事件的映射;
  • 待办完成后,先更新云端状态,再删除对应日程。

日历负责设备重启后的恢复、系统通知和用户可见的时间线。APK 需要 READ_CALENDARWRITE_CALENDAR 权限。

用户可以在日历里修改或删掉事件,但日历不是任务真相源。下一次 APK 刷新时,云端仍然是准的:该存在的事件会被补回,已完成任务的事件会被清掉。

定时:使用 APK 自己的 AlarmManager#

定时任务不写日历。APK 用 AlarmManager 登记本地提醒,到点由 BroadcastReceiver 直接发系统通知。

一次性定时使用时间戳;每天、每周、每月这几种重复规则会在触发后计算下一次并重新登记。重启、系统时间变化和时区变化后,Receiver 会把已登记的任务恢复回来。

Android 12 以后,精确闹钟还涉及“闹钟和提醒”特殊权限。拿不到权限时应用会退回 setAndAllowWhileIdle(),提醒可能不再精确到分钟。这比静默失败好,也比假装能保证到达更诚实。

这里的“闹钟”是 APK 的本地提醒,不是把一条记录无声地塞进系统时钟 App。第三方应用不能这样做;若真要创建系统时钟的闹钟,只能跳转到时钟 App,由用户确认。

真相源不是一台机器,而是任务的 origin#

后来还有一个更棘手的问题:Telegram、本机和 ECS 都能看到任务。如果每一端都可以修改同一条记录,日历、闹钟和 Telegram 很快就会重复提醒或互相删错。

现在把任务分成两个 origin:

origin创建与修改执行提醒APK 的本地投影
1APK、Web、ECSAPK待办进日历;定时进 AlarmManager
0Telegram、本机 mini-agent本机 mini-agent只展示,不创建日历或闹钟

一条任务的身份是 (origin, id),不能只用 id。两个端都可能有 id=1,但它们不是同一条任务。

本机与 ECS 保留每日双向同步:同步的是两个 origin 的记录集合和状态,不改变 origin。ECS 上的 LOCAL_ORIGIN=1,本机默认 0。服务端也会拒绝显式跨 origin 的写请求。

这样做以后,APK/Web 创建的任务不会被 Telegram 端接管;Telegram 创建的任务也不会在手机上再响一遍。两个端都能看见任务全集,但各自只负责自己的分区。

APK 刷新时做 reconcile#

没有 FCM,网页刚创建的任务不可能立刻出现在一台未打开的手机上。这是当前方案明确接受的边界。

补救不是在后台偷偷保活,而是让 APK 在用户进入“待办”或“定时”Tab、或手动刷新时从 ECS 拉取完整快照,然后做一次 reconcile:

  1. 读取 ECS 返回的任务集合;
  2. 忽略或只读展示 origin=0 任务;
  3. 为仍有效的 origin=1 待办补建日历事件;
  4. 为仍有效的 origin=1 定时补建本地闹钟;
  5. 清理任务已完成、已删除或一次性时间已过的本地投影。

网页直接创建的定时任务,仍需要 APK 在到点前至少打开一次对应 Tab 才能在手机上登记闹钟。这不是“实时推送”,但对个人工具来说,行为是可解释的:任务在云端保存,手机在刷新时把它落到本机系统能力里。

收尾#

这次没有得到一个万能推送方案。

FCM 受 GMS 和服务端网络限制;厂商推送受资质和接入成本限制;让本机转发 FCM 又要维护实时同步。最后留下的是更贴近任务语义的组合:待办交给系统日历,定时交给本地闹钟,Telegram 保持本机任务的提醒出口。

它不是跨设备实时提醒系统。它是一个个人工具在现有网络和资质条件下,能稳定解释、能维护、也不需要藏着掖着的提醒方案。

MiniAgent 的 Android 提醒:从 FCM 到系统日历与本地闹钟
https://sgjki547.top/posts/mini-agent-android-reminder-architecture/
Author
SGJki
Published at
2026-08-01
License
CC BY-NC-SA 4.0