一个定位请求不只是调用插件
定位状态容器负责恢复缓存的区域信息并订阅插件事件流。页面和请求层读取统一的区域状态,避免各自保存相互矛盾的位置数据。
定位请求应明确精度、单次或连续模式,以及是否需要地址反查。设备定位负责返回坐标,业务层再决定是否调用区域解析服务。
拿到经纬度后,应用调用区域解析接口,更新共享状态并异步保存缓存。这样插件只负责设备坐标,业务层则消费统一的 区域数据模型。
下面的示例把定位、区域解析与缓存分开,并用请求序号忽略已经过期的结果:
int _requestVersion = 0;
Future<void> refreshLocation() async {
final version = ++_requestVersion;
try {
final position = await readPositionWithFallback();
if (version != _requestVersion) return;
final region = await regionRepository.resolve(position);
if (version != _requestVersion) return;
regionState.value = region;
await regionCache.save(region);
} on TimeoutException {
if (version == _requestVersion) {
regionState.value ??= cachedRegion;
}
} finally {
await locationProvider.stop();
}
}
超时和备用定位能力
单次定位请求设置 15 秒超时。默认使用高精度模式;首次失败或超时后可以尝试一次平台备用定位能力。回退只允许一次,避免失败事件反复重启定位。
高精度定位
↓ 失败或超时
尚未尝试回退?──是──→ 平台备用能力 重试一次
│ 否
↓
停止插件、结束等待、显示失败提示
失败被当作可预期业务结果处理:停止定位、结束 Completer 并记录日志。这样页面可以继续使用缓存或默认城市,不会因为权限拒绝把整段启动流程变成未处理异常。
定位更新会影响多个页面
区域解析成功后,定位状态容器会通知首页和相关内容列表刷新。首页也可以先用缓存坐标或默认城市拉首屏,再由实时定位成功后的刷新替换附近数据。这个设计减少了首屏对 GPS 的硬等待。
需要留意数据范围:HTTP 拦截器当前会在所有请求上附带城市和经纬度头。定位上下文对附近推荐有用,但登录、支付或其他不需要位置的请求是否也应携带精确坐标,适合逐类确认并缩小范围。
生命周期和竞态边界
定位事件流在 onClose() 中取消订阅,并销毁插件;本地缓存读取和写入也单独捕获格式错误。设备验证时仍应覆盖这些情况:
- 用户拒绝或撤销系统定位权限。
- GPS 关闭、网络断开、区域解析失败。
- 高精度定位超时后,旧回调晚于回退结果到达。
- 页面关闭时定位或区域解析请求还未结束。
- 只有缓存位置但用户当前关闭定位权限。
特别是异步请求可能跨越一次超时重试;如果旧请求晚到,最好用请求序号区分新旧结果,避免旧回调完成新请求的等待器。
经验总结
先诚实说:地图定位 SDK 我自己的项目里没接过,这段是拆示例应用时逐行验证的。requestVersion 这套序号防旧回调的写法,我把核心逻辑摘出来用 dart 实际跑过:慢请求先发、快请求后到时,旧回调确实被丢弃,状态只保留最新结果,断言全过。超时后回退平台备用能力的思路我认同,但”只回退一次”这条要写进需求——失败事件反复重启定位能把电量吃光。另外 HTTP 拦截器给所有请求带经纬度头这个细节值得警惕:登录、支付请求不需要位置,精确坐标能少传就少传,合规和省流量都赚。
小结
定位链路可以拆成五步:检查权限、启动一次设备定位、处理错误及回退、调用区域解析服务、缓存并通知业务页面。把每一步的状态和失败结果写清楚,比单纯“请求定位”更容易维护,也方便在不同平台上定位问题。
