Skip to content

地点提醒常驻守护在进程被杀后无法恢复投递(上游 expo-location/expo-task-manager 已知缺陷,非应用层 bug) #413

Description

@LUPENGHAN

地点提醒常驻守护在进程被杀后无法恢复投递(上游 expo-location/expo-task-manager 已知缺陷,非应用层 bug)

现象

设备(小米 MIUI)出圈后走回围栏内,地点提醒不触发。且必须在同一个 App 进程存活期间内出圈+回圈才会响;只要期间进程被系统杀掉(force-stop 或系统后台回收)后重开,之后的出圈/回圈就再也不会触发提醒——直到卸载重装。

关键证据:系统在正常投递,JS 收不到

adb shell dumpsys location 证实:进程被杀重开之后,系统 LocationManager 里对应的 GPS 订阅一直是活的、按注册间隔(15s)持续投递样本:

service: ProviderRequest[@+15s0ms, HIGH_ACCURACY, WorkSource{... com.anonymous.timeflow}]
listeners:
  .../fused_location_provider/4E23CD76 Request[@+15s0ms HIGH_ACCURACY ...]
...
22:32:01.869: gps provider delivered location[1] to .../4E23CD76
22:32:16.869: gps provider delivered location[1] to .../4E23CD76
22:32:31.866: gps provider delivered location[1] to .../4E23CD76
22:32:45.862: gps provider delivered location[1] to .../4E23CD76

同一时间段,App 内 expo-task-managerdefineTask 回调(本项目里打了诊断日志 [guard] dispatching sample to the live listener完全没有任何输出。前台常驻通知(startLocationUpdatesAsyncforegroundService 通知)全程正常显示,服务本身没有被系统拆掉。

结论:断点精确在 expo-location 把原生位置事件路由回 expo-task-manager 注册的 JS defineTask 回调这一段——原生侧一切正常,JS 侧收不到。

复现步骤

  1. 创建一条地点类型日程,走到围栏外触发 armed
  2. 强杀 App 进程(adb shell am force-stop <package>,或让系统自然回收)
  3. 重新打开 App
  4. 走回围栏内

预期:触发提醒。实际:不触发,且此后任何出圈/回圈都不再触发,直到卸载重装。

冷启动时会有一次性的例外:ExpoLocationMonitor.rebuild()frontend/src/infrastructure/location/ExpoLocationMonitor.ts)在 watch()/rebuild() 时会主动取一次当前定位喂给状态机;如果出圈时 geofence_armed 已经持久化为 true 且此刻恰好在圈内,这一次性采样会补一条提醒。这与后台持续监控是否恢复无关,容易造成"重启后会提醒一次"的误判。

已排查、已确认无效的应用层修法

  1. stopLocationUpdatesAsync + startLocationUpdatesAsync 重建注册(ReminderGuardCoordinator.ensureLocationUpdates
  2. 额外补一次 TaskManager.unregisterTaskAsync() 强制清空持久化任务记录后再重新注册
  3. 换任务名方案评估:理论上单次冷启动有效,但因为 defineTask() 必须在模块顶层同步调用(否则 headless 唤醒时找不到对应 handler),无法安全地用运行时动态生成的任务名实现,评估后放弃

根因与上游状态

这是 expo-location/expo-task-manager 在 Android 上处理"进程被杀后台任务恢复"的已知问题类别,多个 SDK 大版本反复出现:

  • expo/expo#28959 — SDK 51,startLocationUpdatesAsync 回调收不到任何位置更新
  • expo/expo#23559 — Android 上 App 从不以 headless=true 执行,OS 唤醒时若没有 React 树挂载,任务收不到东西
  • expo/expo#3535 — 进程被杀后 hasStartedGeofencingAsync()/getRegisteredTasksAsync() 有时直接返回空
  • expo/expo#47673 — 症状与本项目高度一致("进程被系统杀掉后地点更新停止投递,只有完整重装能恢复"),已关闭,修复见 expo/expo#47958

#47958 的根因(TaskService.java):sHeadlessTaskManagers 会在 invalidateApp() 后被提前清空,此时 Android context 实际还活着(不满足重建条件),导致 getTaskManager() 此后一直返回 null——事件不是没收到,是收到了却找不到管理器处理,静默丢进队列。

该修复尚未发布到任何稳定 SDK 补丁版本(已核实 57.0.9/57.0.14/56.0.27 均不含此修复,仅存在于未转正的 58.0.0-canary 分支)。

已应用的缓解

  • 本仓库已通过 patch-package#47958 的 diff 打进 node_modules/expo-task-manager(见 frontend/patches/expo-task-manager+57.0.9.patch),postinstall 自动生效,无需手动操作
  • ReminderGuardCoordinatorfrontend/src/features/reminder/application/ReminderGuardCoordinator.ts)新增了"陈旧检测":不再拿注册 options 当"还在投递"的证据,改用"本进程是否自己成功建过注册 + 最近是否收到过心跳"判断,检测到继承自上一个(已死)进程的注册时会主动重建(含真正的 TaskManager.unregisterTaskAsync()

补丁和检测逻辑均未能确认彻底解决问题(受限于测试条件未完成完整的真机出圈/回圈验证),但作为已知修复方向保留,不产生副作用。

结论

这是 Android 平台 expo-location/expo-task-manager 处理进程被杀后台任务恢复的上游限制,不是 Timeflow 自身业务代码的 bug。当前没有已验证的、可靠的应用层完整解法。已采取的缓解(升级到含 #47958 修复的 patch)方向正确但未获完整验证。

已知限制:地点类提醒在 App 进程被系统杀掉后可能失效,需要重新打开 App 才能一次性补触发(若出圈时已 armed),此后持续监控能否恢复不保证。时间类提醒不受影响(走独立的原生闹钟 TimeflowAlarm 机制)。

演示/验收时应避免中途强杀或长时间后台放置 App。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions