Android Kotlin中fusedLocationProviderClient.getCurrentLocation异常问题
问题
开发一款Android(Kotlin)应用,需求为按需获取位置(仅点击悬浮按钮时获取经纬度并显示在TextView中),无需持续更新位置(不使用requestLocationUpdates)。权限检查逻辑已实现且无问题。
在Motorola One Vision(Android 11)设备上步行测试时,出现异常行为:
- 首次点击定位按钮数秒后获取到坐标;
- 步行5-10米(约8-15秒)后再次点击按钮,会出现三种情况:
- 3-5秒后获取到新坐标,状态栏显示谷歌定位图标,此为正常情况;
- 立即获取到步行前的旧坐标,状态栏无谷歌定位图标,瞬间弹出“Location obtained”提示Toast,需等待数秒后多次点击才能获取新坐标;
- 瞬间获取到新坐标,弹出“Location obtained”提示Toast,但状态栏无谷歌定位图标。
疑问:
- 第二种情况产生的原因是什么?如何解决,实现每次点击都获取新坐标(即使需等待数秒)?
- 第三种情况的原因是什么?
触发定位的代码如下:
@SuppressLint("MissingPermission") fun getLocation(){ if (isGPSActivated){ fusedLocationProviderClient.getCurrentLocation( LocationRequest.PRIORITY_HIGH_ACCURACY, object : CancellationToken() { override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token override fun isCancellationRequested() = false }) .addOnSuccessListener { location: Location? -> if (location == null) { Toast.makeText( this, "Can't get location, try again", Toast.LENGTH_SHORT ).show() } else { updateLocationInViewModel(location) Toast.makeText( this, "Location obtained", Toast.LENGTH_SHORT ).show() } } .addOnFailureListener { Toast.makeText( this, "Can't get location, try again", Toast.LENGTH_SHORT ).show() } } } fun updateLocationInViewModel(currentLocation: Location){ individuoViewModel._localization.value = currentLocation }
已为individuoViewModel._localization设置观察者,检测到新值时会刷新TextView。
解答
第二种情况(返回旧坐标)的原因与解决方法
原因
getCurrentLocation()的核心逻辑是优先返回系统缓存的最近位置,只要这个缓存位置的时间戳在系统允许的“新鲜度”范围内(高精准度优先级下通常是几秒到几十秒),就不会触发新的定位请求,直接返回缓存值。
你步行后短时间内点击按钮时,缓存的旧位置还未被系统判定为过期,因此直接返回旧坐标,不会唤起定位硬件(状态栏无图标),也不会等待新的定位结果。
解决方法
要强制获取最新位置,需绕过getCurrentLocation()的缓存逻辑,推荐使用单次定位回调的方式:
@SuppressLint("MissingPermission") fun getLocation() { if (isGPSActivated) { val locationRequest = LocationRequest.create().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY numUpdates = 1 // 仅请求一次定位更新 fastestInterval = 0 interval = 0 } val locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) locationResult.lastLocation?.let { location -> updateLocationInViewModel(location) Toast.makeText(this@YourActivity, "Location obtained", Toast.LENGTH_SHORT).show() } ?: run { Toast.makeText(this@YourActivity, "Can't get location, try again", Toast.LENGTH_SHORT).show() } // 立即移除回调,避免持续监听 fusedLocationProviderClient.removeLocationUpdates(this) } } fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()) .addOnFailureListener { Toast.makeText(this@YourActivity, "Can't get location, try again", Toast.LENGTH_SHORT).show() fusedLocationProviderClient.removeLocationUpdates(locationCallback) } } }
如果坚持使用getCurrentLocation(),可以在返回结果时校验位置新鲜度,拒绝过期的旧坐标并重新请求:
// 在addOnSuccessListener的else分支中添加判断 else { val locationAge = System.currentTimeMillis() - location.time if (locationAge > 5000) { // 超过5秒的位置视为无效 getLocation() // 重新发起请求 return@addOnSuccessListener } updateLocationInViewModel(location) Toast.makeText(this, "Location obtained", Toast.LENGTH_SHORT).show() }
第三种情况(返回新坐标但无状态栏图标)的原因
这种情况属于系统优化行为:此时系统缓存中已经存在足够新鲜的最新位置——可能是其他应用刚发起过定位请求,或者系统后台定位服务刚更新了位置,你的应用直接获取到缓存的新坐标,无需自己触发定位硬件,因此状态栏不会显示定位图标。这是正常现象,无需额外处理。
内容的提问来源于stack exchange,提问作者Daniel Florez
相关产品推荐
相关产品推荐

