Expo Location的watchPositionAsync无返回或长时间延迟问题求助
核心问题原因
- 执行顺序错误:在未获取定位权限前就调用
watchPositionAsync初始化定位监听,权限未授予时定位接口直接静默失败,导致长期停留在Waiting状态 - 状态访问逻辑错误:第一版代码中
deviceLocation初始值为null,直接访问deviceLocation.location会抛出未定义错误,阻塞后续逻辑执行 - React状态异步陷阱:第二版代码中
setDeviceLocation是异步操作,调用后立刻执行getDistfromTreasure、switchDist时读取到的还是更新前的旧状态,导致逻辑异常,且多次连续调用setDeviceLocation会触发不必要的多次重渲染,导致更新间隔和配置的timeInterval不一致 - 缺少定位订阅清理逻辑:未存储
watchPositionAsync返回的订阅实例,也没有在组件卸载时销毁订阅,会产生内存泄漏,同时多次重复初始化订阅会导致定位回调冲突,表现为定位返回不稳定 - 双触发规则影响:同时配置
timeInterval和distanceInterval时,两个条件满足任意一个就会触发回调,移动速度变化时就会出现回调间隔波动
修复后的代码示例
import React, {useState, useEffect, useRef} from 'react'; import { View, Text } from 'react-native'; import * as Location from 'expo-location'; import { getDistance } from 'geolib'; const treasureLocation = { latitude: 39.2695552, longitude: 8.4679172 } const Navigator = () => { const [deviceLocation, setDeviceLocation] = useState({ location: null, treasureDist: 0, sentence: '' }); const [errorMsg, setErrorMsg] = useState(null); // 存储定位订阅实例,用于清理 const locationSubscription = useRef(null); const getLocationAsync = async() => { // 先判断权限状态再初始化监听 const { status } = await Location.getForegroundPermissionsAsync(); if (status !== 'granted') return; // 先销毁已存在的订阅,避免重复初始化 if (locationSubscription.current) { locationSubscription.current.remove(); } locationSubscription.current = await Location.watchPositionAsync({ accuracy: Location.Accuracy.BestForNavigation, timeInterval: 10000, distanceInterval: 20 }, (newLocation) => { // 单次计算所有需要更新的字段,只调用一次setState const { latitude: deviceLat, longitude: deviceLong } = newLocation.coords; const dist = getDistance( { latitude: deviceLat, longitude: deviceLong }, treasureLocation ); let sentence = ''; if (dist > 1000) { sentence = 'Example1'; } else if (dist > 500) { sentence = 'Example2'; } else if (dist > 100) { sentence = 'Example3'; } else if (dist > 50) { sentence = 'Example4'; } else { sentence = 'Example5'; } setDeviceLocation({ location: newLocation, treasureDist: dist, sentence }); }); }; useEffect(() => { let isMounted = true; (async () => { let { status } = await Location.requestForegroundPermissionsAsync(); if (!isMounted) return; if (status !== 'granted') { setErrorMsg('Permission to access location was denied'); return; } // 权限申请成功后再初始化定位监听 getLocationAsync(); })(); // 组件卸载时清理订阅 return () => { isMounted = false; if (locationSubscription.current) { locationSubscription.current.remove(); } } }, []); let text = 'Waiting..'; if (errorMsg) { text = errorMsg; } else if (deviceLocation.location) { text = JSON.stringify(deviceLocation.location); } return ( <> <View> <Text>{text}</Text> </View> <View> <Text>{deviceLocation.sentence}</Text> </View> </> ) } export default Navigator
额外优化建议
- 真机测试时开启设备高精度定位模式,避免仅使用网络定位导致的精度差、返回慢问题
- 若需要后台持续定位,额外申请后台定位权限,否则应用切后台后定位会被系统暂停
- 室内环境GPS信号弱时可适当降低精度要求,加快定位返回速度
内容的提问来源于stack exchange,提问作者Aurora
相关产品推荐
相关产品推荐

