Skip to content

Android 定位采样在实时定位不可用时缺少可用兜底 #338

Description

@Alexander-Noah

执行摘要

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;

验收标准

  • Android 系统存在 last-known location 时,业务能够获得有效位置。
  • Expo current position 返回 ERR_CURRENT_LOCATION_IS_UNAVAILABLE 时可以回退 native cached location。
  • 语音握手能够使用 native cached location。
  • geofence 采样能够使用 native cached location。
  • native 缓存不可用时仍可尝试 Expo 实时定位。
  • 所有定位来源都不可用时安全返回 null
  • 非法坐标和不完整 payload 不会进入业务状态。
  • 定位超过 500ms 时不会阻塞语音连接。
  • 后台权限状态读取失败不会产生未捕获异常。
  • development build 可以加载新增 native module。
  • 相关单元测试和类型检查通过。
  • Android native 主源码和测试源码编译通过。

测试覆盖

  • 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 后台定位权限约束。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions