Skip to content

fix(reminder): SQLite 连接生命周期崩溃 + 围栏半径/轮询策略重设计 #356

Description

@LUPENGHAN

背景

提醒相关操作反复出现 SQLite NPE 崩溃:expo-modules-coreopenDatabaseAsync() 在缓存命中时会给同一条原生连接分配新的 SharedObjectId,第二个 JS 侧代理对象被 GC 时会把共享的原生连接一起关掉,导致仍在使用中的第一个代理对象崩溃。

围栏侧还有三个可靠性缺口:

  1. AlarmModule.presentNow() 目前收到调用就立即 resolve(true),不等 AlarmSoundService 真的展示成功/失败/超时,调用方拿到的"展示成功"信号不可信。
  2. 提醒进入 pending 状态后,如果用户一直没有确认/延后,没有任何机制发现并救回,会一直卡住。
  3. 围栏默认半径 200m 偏小、轮询间隔按"离中心点距离"计算,边界附近容易错过触发窗口;ReminderGuardCoordinator 用本地间隔变化阈值判断"要不要重新注册",没有对照原生真实运行状态,日程列表变化时可能跟任务自己的执行窗口撞车,引发并发访问 SQLite。

方案

  • sqlite.ts 改成真正的 JS 侧单例连接,所有独立调用点共享同一个 openDatabaseAsync() 结果,不再各自打开各自关闭。
  • 新增 accessGate.ts:严格 FIFO 队列串行化所有数据库访问——连接现在是全应用共享的,必须序列化。
  • AlarmModule.presentNow() 等待 AlarmSoundService 回报的真实成功/失败/超时信号,不再调用后立即 resolve。
  • LocalReminderApplication 新增救回机制:提醒 disposition 卡在 pending 超过 2 分钟(会话存活期间)时主动重新判定。
  • 围栏默认半径 200m → 400m;轮询间隔改成按"离边界距离"(半径作为近距阈值)计算,不再按"离中心点距离"。
  • ReminderGuardCoordinator.ensureLocationUpdates() 改成先查 Location.hasStartedLocationUpdatesAsync() 的原生真实状态,只有确认没在跑时才重新调用 startLocationUpdatesAsync(),不再用本地间隔变化阈值同步触发重新注册。

验收标准

  • 连续快速创建/删除多条提醒不再触发 SQLite NPE 崩溃。
  • presentNow() 的返回值能反映 AlarmSoundService 的真实展示结果。
  • 提醒卡在 pending 超过 2 分钟后,应用在会话存活期间能自动救回。
  • 围栏默认半径改为 400m,轮询间隔在靠近边界时明显加密。
  • 现有 JS/原生单测全部通过。

不做

规格:MiniSpec

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions