3. 使用 FusedLocationProviderClient 获取最后已知位置
为什么 getLastLocation 是“先问缓存,再决定要不要动”
FusedLocationProviderClient 的 getLastLocation() 返回的是系统位置缓存中最近一次被任意应用写入的位置,它不触发新的定位硬件工作,因此通常毫秒级返回,但可能很旧、精度很差,甚至为 null。正确用法不是“拿到就用”,而是先判断时效性与精度,再决定是否发起新的定位请求。
时效性判断的三个维度
- 是否为空:返回 null 表示缓存中没有任何可用位置,必须发起新请求。
- 时间戳 age:
System.currentTimeMillis() - location.getTime(),超过阈值(如 2 分钟)视为过期。 - 精度 accuracy:
location.getAccuracy()单位为米,数值越大越差,超过阈值(如 100 m)视为不可用。
缓存位置适合“先渲染再刷新”的体验:立即把旧位置画到界面上,同时后台发起一次新请求,新结果到达后覆盖。这样用户不会看到空白等待。
完整实现:缓存优先 + 过期回退
class LastKnownLocationActivity : AppCompatActivity() {
private lateinit var fusedClient: FusedLocationProviderClient
private val maxAgeMs = 2 * 60 * 1000L // 缓存最大可接受年龄:2 分钟
private val maxAccuracyM = 100f // 缓存最大可接受精度:100 米
private val locationPermissionRequest =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grants ->
if (grants[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
grants[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {
readLastKnownLocation()
} else {
showMessage("未授予定位权限,无法读取缓存位置")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedClient = LocationServices.getFusedLocationProviderClient(this)
requestPermissionThenRead()
}
private fun requestPermissionThenRead() {
val fine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
val coarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
if (fine == PackageManager.PERMISSION_GRANTED || coarse == PackageManager.PERMISSION_GRANTED) {
readLastKnownLocation()
} else {
locationPermissionRequest.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION))
}
}
@SuppressLint("MissingPermission")
private fun readLastKnownLocation() {
fusedClient.lastLocation
.addOnSuccessListener { location -> handleLastLocation(location) }
.addOnFailureListener { e -> showMessage("读取缓存位置失败:${e.message}") }
}
private fun handleLastLocation(location: Location?) {
if (location == null) {
showMessage("缓存为空,需要发起新定位请求")
requestFreshLocation()
return
}
val ageMs = System.currentTimeMillis() - location.time
val accuracy = location.accuracy
val fresh = ageMs <= maxAgeMs && accuracy <= maxAccuracyM
renderLocation(location, ageMs, accuracy, fresh)
if (!fresh) {
showMessage("缓存已过期或精度不足,后台刷新中…")
requestFreshLocation()
}
}
@SuppressLint("MissingPermission")
private fun requestFreshLocation() {
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L)
.setMaxUpdates(1)
.build()
fusedClient.requestLocationUpdates(request, object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { fresh ->
renderLocation(fresh, 0L, fresh.accuracy, true)
}
fusedClient.removeLocationUpdates(this)
}
}, Looper.getMainLooper())
}
private fun renderLocation(loc: Location, ageMs: Long, accuracy: Float, fresh: Boolean) {
val tag = if (fresh) "可用" else "过期"
showMessage("[$tag] lat=${loc.latitude}, lon=${loc.longitude}, " +
"age=${ageMs / 1000}s, acc=${accuracy}m")
}
private fun showMessage(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}
判断逻辑可视化
关键参数与判读
| 参数 | 含义 | 示例阈值 | 超限处理 |
|---|---|---|---|
location == null | 缓存中无任何位置 | — | 直接发起新请求 |
ageMs | 位置距今毫秒数 | ≤ 120000 | 视为过期,后台刷新 |
accuracy | 水平精度(米) | ≤ 100 | 视为不可用,后台刷新 |
getLastLocation 返回的 Location 可能来自其他应用写入的缓存,其精度与时间戳不一定满足你的业务需求。不要跳过时效性判断直接使用,否则可能把几分钟甚至几小时前的位置当作当前位置展示。
验证方法
- 首次安装后立即调用:预期返回 null,触发新请求路径。
- 打开地图应用定位一次后回到本页:预期返回非 null,age 较小,直接使用缓存。
- 关闭定位服务后调用:预期返回 null 或旧位置,观察是否走新请求分支并收到失败回调。
- 用 Toast 输出 age 与 accuracy,确认阈值判断与预期一致。