执行摘要
Android 真机可能已经保存了有效的 last-known location,但 Expo getCurrentPositionAsync 仍会返回 ERR_CURRENT_LOCATION_IS_UNAVAILABLE,导致语音握手和 geofence 初始化拿不到位置。
本 Issue 的目标是在 Expo 定位不可用时读取 Android 系统缓存位置,并限制语音启动阶段的定位等待时间,保证“有可用缓存时能够定位、没有定位时也不阻塞语音”。
用户现象
Android development build 出现以下日志:
WARN [location-probe] native cached location unavailable
ERROR [geofence] getCurrentSample failed ERR_CURRENT_LOCATION_IS_UNAVAILABLE
但 Android 系统实际上已经存在可用位置(以下坐标已脱敏):
provider=network
latitude=12.345678
longitude=98.765432
accuracy=31.0
这说明设备并非完全无法定位,而是 Expo 实时定位失败后,业务没有继续使用 Android 系统保存的位置。
真实调用链
修复前:
语音握手或 geofence 请求位置
-> Expo 缓存位置不可用
-> Expo 实时定位失败或等待过久
-> 返回 null
-> Android 系统缓存位置未被使用
期望链路:
业务请求位置
-> 优先读取 Expo 短期缓存
-> Expo 缓存不可用时读取 Android native last-known location
-> native 缓存可用时立即返回
-> native 缓存不可用时尝试 Expo 实时定位
-> 所有来源均不可用时安全返回 null
直接证据
Android LocationManager 已保存以下缓存位置(以下坐标已脱敏):
TimeflowLocation: getLastKnownLocation
provider=network
lat=12.345678
lon=98.765432
accuracy=31.0
原生缓存位置被 JavaScript 侧使用后,日志为:
[location-search] using native cached location
因此 Android native last-known location 可以作为 Expo 定位失败时的兜底数据源。
修复方案
1. 增加 Android 原生定位模块
新增 TimeflowLocation native module,通过 Android LocationManager 读取:
network
passive
gps
fused
使用 LocationSnapshotSelector 选择位置:
- 优先选择观测时间更新的样本;
- 时间相同时选择精度更高的样本;
- 没有有效样本时返回
null。
2. 增加 JavaScript 原生定位适配层
新增 NativeLocationFallback.ts,将原生结果转换为统一结构:
{
latitude,
longitude,
accuracy_meters,
observed_at
}
原生模块不存在、调用失败、字段缺失或坐标非法时返回 null。
3. 接入语音定位
调整 ExpoLocationProvider:
- 优先使用 Expo 短期缓存;
- Expo 缓存不可用时读取 native cached location;
- native 缓存可用时立即返回;
- 后台继续刷新 Expo 实时位置;
- native 缓存不可用时再尝试 Expo current position。
4. 接入 geofence 定位采样
调整 ExpoLocationMonitor:
- Expo current position 失败时回退 native cached location;
- native 缓存可用时更新
lastSample;
- 后台权限状态读取失败时安全跳过 geofence 同步;
- 避免产生未捕获的 Promise rejection。
5. 避免定位阻塞语音启动
调整:
AssistantConversationService
AssistantContinuousConversationService
将语音启动阶段的定位等待预算限制为 500ms。
超过等待时间后:
- 语音连接继续启动;
- 本次 location sample 使用
null;
- 不继续阻塞当前会话。
6. 增加开发环境探针
开发环境设置:
EXPO_PUBLIC_LOCATION_PROBE_ON_START=1
应用启动后输出 native cached location 是否可用,方便在 Android 真机验证定位兜底。
7. 补充 development build 配置
- 增加
expo-dev-client;
- 配置
timeflow URL scheme;
- 支持 development client deep link;
验收标准
测试覆盖
LocationSnapshotSelector:最新位置、同时间精度比较、空列表。
NativeLocationFallback:数据映射、空结果、异常和非法 payload。
ExpoLocationProvider:Expo 缓存、native fallback、实时定位 fallback。
ExpoLocationMonitor:current position 失败后的 native fallback。
- Assistant services:慢定位不阻塞语音连接。
范围与非目标
本 Issue 包含:
- Android 系统缓存定位读取;
- Expo 定位失败后的 native fallback;
- 语音定位等待上限;
- geofence 定位采样兜底;
- development build 定位调试支持。
本 Issue 不包含:
- 完整的后台定位授权流程;
- 强制申请后台定位权限;
- 解决
ACCESS_BACKGROUND_LOCATION 权限配置问题;
- 修改 geofence enter/exit 状态机;
- 保证每次获得实时 GPS 坐标;
- iOS 原生缓存定位实现。
风险与依赖
- last-known location 可能不是实时坐标,需要保留观测时间和精度。
- 不同 Android 设备提供的 location provider 可能不同。
- native module 需要重新构建 development build,Expo Go 无法加载。
- geofence 后台运行仍受 Android 后台定位权限约束。
执行摘要
Android 真机可能已经保存了有效的 last-known location,但 Expo
getCurrentPositionAsync仍会返回ERR_CURRENT_LOCATION_IS_UNAVAILABLE,导致语音握手和 geofence 初始化拿不到位置。本 Issue 的目标是在 Expo 定位不可用时读取 Android 系统缓存位置,并限制语音启动阶段的定位等待时间,保证“有可用缓存时能够定位、没有定位时也不阻塞语音”。
用户现象
Android development build 出现以下日志:
但 Android 系统实际上已经存在可用位置(以下坐标已脱敏):
这说明设备并非完全无法定位,而是 Expo 实时定位失败后,业务没有继续使用 Android 系统保存的位置。
真实调用链
修复前:
期望链路:
直接证据
Android
LocationManager已保存以下缓存位置(以下坐标已脱敏):原生缓存位置被 JavaScript 侧使用后,日志为:
因此 Android native last-known location 可以作为 Expo 定位失败时的兜底数据源。
修复方案
1. 增加 Android 原生定位模块
新增
TimeflowLocationnative module,通过 AndroidLocationManager读取:networkpassivegpsfused使用
LocationSnapshotSelector选择位置:null。2. 增加 JavaScript 原生定位适配层
新增
NativeLocationFallback.ts,将原生结果转换为统一结构:原生模块不存在、调用失败、字段缺失或坐标非法时返回
null。3. 接入语音定位
调整
ExpoLocationProvider:4. 接入 geofence 定位采样
调整
ExpoLocationMonitor:lastSample;5. 避免定位阻塞语音启动
调整:
AssistantConversationServiceAssistantContinuousConversationService将语音启动阶段的定位等待预算限制为 500ms。
超过等待时间后:
null;6. 增加开发环境探针
开发环境设置:
应用启动后输出 native cached location 是否可用,方便在 Android 真机验证定位兜底。
7. 补充 development build 配置
expo-dev-client;timeflowURL scheme;验收标准
ERR_CURRENT_LOCATION_IS_UNAVAILABLE时可以回退 native cached location。null。测试覆盖
LocationSnapshotSelector:最新位置、同时间精度比较、空列表。NativeLocationFallback:数据映射、空结果、异常和非法 payload。ExpoLocationProvider:Expo 缓存、native fallback、实时定位 fallback。ExpoLocationMonitor:current position 失败后的 native fallback。范围与非目标
本 Issue 包含:
本 Issue 不包含:
ACCESS_BACKGROUND_LOCATION权限配置问题;风险与依赖