背景
提醒相关操作反复出现 SQLite NPE 崩溃:expo-modules-core 的 openDatabaseAsync() 在缓存命中时会给同一条原生连接分配新的 SharedObjectId,第二个 JS 侧代理对象被 GC 时会把共享的原生连接一起关掉,导致仍在使用中的第一个代理对象崩溃。
围栏侧还有三个可靠性缺口:
AlarmModule.presentNow() 目前收到调用就立即 resolve(true),不等 AlarmSoundService 真的展示成功/失败/超时,调用方拿到的"展示成功"信号不可信。
- 提醒进入
pending 状态后,如果用户一直没有确认/延后,没有任何机制发现并救回,会一直卡住。
- 围栏默认半径 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(),不再用本地间隔变化阈值同步触发重新注册。
验收标准
不做
规格:MiniSpec
背景
提醒相关操作反复出现 SQLite NPE 崩溃:
expo-modules-core的openDatabaseAsync()在缓存命中时会给同一条原生连接分配新的SharedObjectId,第二个 JS 侧代理对象被 GC 时会把共享的原生连接一起关掉,导致仍在使用中的第一个代理对象崩溃。围栏侧还有三个可靠性缺口:
AlarmModule.presentNow()目前收到调用就立即resolve(true),不等AlarmSoundService真的展示成功/失败/超时,调用方拿到的"展示成功"信号不可信。pending状态后,如果用户一直没有确认/延后,没有任何机制发现并救回,会一直卡住。ReminderGuardCoordinator用本地间隔变化阈值判断"要不要重新注册",没有对照原生真实运行状态,日程列表变化时可能跟任务自己的执行窗口撞车,引发并发访问 SQLite。方案
sqlite.ts改成真正的 JS 侧单例连接,所有独立调用点共享同一个openDatabaseAsync()结果,不再各自打开各自关闭。accessGate.ts:严格 FIFO 队列串行化所有数据库访问——连接现在是全应用共享的,必须序列化。AlarmModule.presentNow()等待AlarmSoundService回报的真实成功/失败/超时信号,不再调用后立即 resolve。LocalReminderApplication新增救回机制:提醒 disposition 卡在pending超过 2 分钟(会话存活期间)时主动重新判定。ReminderGuardCoordinator.ensureLocationUpdates()改成先查Location.hasStartedLocationUpdatesAsync()的原生真实状态,只有确认没在跑时才重新调用startLocationUpdatesAsync(),不再用本地间隔变化阈值同步触发重新注册。验收标准
presentNow()的返回值能反映AlarmSoundService的真实展示结果。pending超过 2 分钟后,应用在会话存活期间能自动救回。不做
规格:MiniSpec