1. 获取单次定位并显示经纬度
任务拆解:一次定位请求的完整链路
本例要完成的是一个最小可用的“单次定位”功能:点击按钮后请求一次高精度位置,把经纬度显示到界面上,同时把权限被拒绝、系统定位开关关闭这两种异常情况处理掉。核心 API 是 FusedLocationProviderClient 的 getCurrentLocation(),它比 getLastLocation() 更适合“单次定位”场景——后者可能返回过期缓存,前者会主动发起一次新的位置计算。
权限与定位开关的前置判断
Android 6.0 起定位权限属于运行时权限,必须在请求位置前检查并申请。同时,即使权限已授予,用户仍可能关闭系统定位开关,此时请求会失败或返回空结果。因此请求前需要做两层判断:
- 权限层:检查
ACCESS_FINE_LOCATION是否已授予;未授予则通过ActivityResultLauncher发起申请。 - 开关层:通过
LocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)判断定位服务是否开启;未开启则引导用户跳转到系统定位设置页。
权限被拒绝后不要反复弹窗,应给出明确提示并保留一个“重新申请”入口;定位开关关闭时,用
Settings.ACTION_LOCATION_SOURCE_SETTINGS 跳转设置页是最稳妥的做法。请求参数与回调处理
调用 getCurrentLocation() 时需要传入 Priority 和 CancellationToken。单次高精度定位使用 Priority.PRIORITY_HIGH_ACCURACY,它会综合 GPS、Wi-Fi、基站给出尽可能精确的结果。返回的 Location 对象中,latitude 和 longitude 就是我们要显示的经纬度,accuracy 可用于判断结果可信度。
// 关键调用片段
fusedClient.getCurrentLocation(
Priority.PRIORITY_HIGH_ACCURACY,
cancellationToken
).addOnSuccessListener { location ->
if (location != null) {
val lat = location.latitude
val lon = location.longitude
val acc = location.accuracy
binding.tvResult.text = "纬度: %.6f\n经度: %.6f\n精度: %.1f m"
.format(lat, lon, acc)
} else {
binding.tvResult.text = "未获取到位置,请检查定位开关"
}
}.addOnFailureListener { e ->
binding.tvResult.text = "定位失败: ${e.message}"
}
异常分支与结果判读
整个流程有三个关键分支需要区分处理:权限被拒绝、定位开关关闭、请求成功但返回 null。前两者属于前置条件不满足,应在发起请求前拦截;后者通常发生在信号极差或系统刚重启时,应提示用户稍后重试。成功时除了显示经纬度,建议同时显示 accuracy,当精度值大于 100 米时提示“当前精度较低”。
验证与常见问题
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 点击后无任何反应 | 权限未申请或申请后未重新检查 | 在 ActivityResultLauncher 回调中重新判断权限再发起请求 |
| 返回 null | 定位开关关闭或信号极差 | 先检查 isProviderEnabled,再提示用户到开阔处重试 |
| 经纬度长时间不变 | 误用了 getLastLocation 的缓存 | 确认调用的是 getCurrentLocation |
| 精度值很大 | 室内或 GPS 信号弱 | 提示用户到室外,或接受 Wi-Fi/基站定位的较低精度 |
不要在主线程中同步等待位置结果;
getCurrentLocation 返回的是 Task,必须通过 addOnSuccessListener / addOnFailureListener 异步处理。另外,请求前应创建 CancellationToken,在页面销毁时取消,避免内存泄漏。