12、位置回调:LocationCallback与onLocationResult、移除位置更新、错误处理机制
位置回调,说白了就是系统把位置数据主动推给你的方式。你不需要轮询,不需要反复问「你在哪」。你只需要注册一个回调,系统有位置更新了,就会自动调用你。
我个人习惯用 LocationCallback 而不是 LocationListener。为什么?因为前者更灵活,能处理更多场景。嗯,咱们今天就把这块彻底讲透。
12.1 LocationCallback 的核心结构
LocationCallback 是一个抽象类,你需要实现它的几个关键方法。我刚开始用的时候,以为只需要实现 onLocationResult 就够了。后来踩了坑才发现,其他方法也很重要。
public class MyLocationCallback extends LocationCallback {
@Override
public void onLocationResult(LocationResult locationResult) {
// 位置更新来了
}
@Override
public void onLocationAvailability(LocationAvailability availability) {
// 位置服务可用性变化
}
}
你想想看,如果只处理位置结果,但位置服务本身不可用了,你还在那傻等,是不是很尴尬?
12.2 onLocationResult 的正确打开方式
onLocationResult 是你最常用的方法。它返回一个 LocationResult 对象,里面可能包含多个位置。为什么会有多个?因为系统可能同时从 GPS、网络、基站拿到了多个位置,它会一起给你。
核心要点:永远从 LocationResult 中取最后一个位置,那是最新的。
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult == null) {
return;
}
// 取最后一个位置,通常精度最高
Location location = locationResult.getLastLocation();
// 或者遍历所有位置
for (Location loc : locationResult.getLocations()) {
// 处理每个位置
}
}
我在项目中遇到过一个问题:取到的位置精度很差,后来发现是因为我取了第一个位置,而不是最后一个。第一个位置往往是缓存的老数据,精度低得离谱。
12.3 注册与移除位置更新
注册回调很简单,调用 requestLocationUpdates 就行。但移除更新很多人会忘。我曾经在线上环境发现电量消耗异常,查了半天,原来是某个页面退出时没移除回调。
重要:Activity 或 Fragment 销毁时,必须移除位置更新。否则回调会一直持有引用,导致内存泄漏。
// 注册
LocationRequest locationRequest = LocationRequest.create()
.setInterval(10000)
.setFastestInterval(5000)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationClient.requestLocationUpdates(locationRequest,
locationCallback, Looper.getMainLooper());
// 移除
locationClient.removeLocationUpdates(locationCallback);
这里有个细节:removeLocationUpdates 传入的 callback 对象,必须和注册时是同一个。如果你 new 了一个新的,那就移除不掉了。嗯,这个坑我踩过两次才记住。
12.4 错误处理机制
位置服务不是永远可靠的。GPS 信号弱、权限被拒绝、位置服务关闭……这些都会导致回调失败。但 LocationCallback 本身没有直接的错误回调方法,那怎么办?
你需要结合 FusedLocationProviderClient 的 getLastLocation 或者 requestLocationUpdates 返回的 Task 来处理异常。
// 注册时捕获异常
Task<Void> task = locationClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper());
task.addOnFailureListener(e -> {
// 处理错误
if (e instanceof SecurityException) {
// 权限问题
} else if (e instanceof IllegalStateException) {
// 位置服务未开启
}
});
另外,onLocationAvailability 方法也能帮你判断位置服务是否可用。如果不可用,你可以提前提示用户,而不是傻等。
@Override
public void onLocationAvailability(LocationAvailability availability) {
if (!availability.isLocationAvailable()) {
// 位置服务不可用,提示用户
showLocationDisabledDialog();
}
}
12.5 知识体系图
下面这张图总结了位置回调的完整流程和关键节点。我建议你保存下来,写代码时对照着看。
12.6 完整示例代码
把上面这些知识点串起来,就是一个完整的实现。我建议你直接复制这段代码,改改就能用。
public class LocationHelper {
private FusedLocationProviderClient locationClient;
private LocationCallback locationCallback;
public LocationHelper(Context context) {
locationClient = LocationServices
.getFusedLocationProviderClient(context);
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult result) {
if (result == null) return;
Location location = result.getLastLocation();
if (location != null) {
// 处理位置
double lat = location.getLatitude();
double lng = location.getLongitude();
}
}
@Override
public void onLocationAvailability(
LocationAvailability availability) {
if (!availability.isLocationAvailable()) {
// 位置不可用,提示用户
}
}
};
}
public void startUpdates() {
LocationRequest request = LocationRequest.create()
.setInterval(10000)
.setFastestInterval(5000)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Task<Void> task = locationClient.requestLocationUpdates(
request, locationCallback, Looper.getMainLooper());
task.addOnFailureListener(e -> {
// 处理注册失败
if (e instanceof SecurityException) {
// 缺少权限
}
});
}
public void stopUpdates() {
locationClient.removeLocationUpdates(locationCallback);
}
}
个人建议:把 LocationHelper 封装成一个单例,整个 App 共享一个位置回调实例。这样既节省资源,又方便管理。
12.7 避坑指南
最后,分享几个我踩过的坑,你遇到了可以直接跳过。
- 回调不触发?检查一下是不是在子线程注册的。回调默认在主线程,如果你在子线程注册,需要传入对应的 Looper。
- 位置一直不变?看看是不是设置了
setSmallestDisplacement,位移阈值设太大,位置就不会更新。 - 内存泄漏?Activity 销毁前一定要调
removeLocationUpdates。我曾经因为这个被测试同学追着报 bug。 - 权限问题?Android 10 以上,后台位置权限需要单独申请。别只申请前台权限就以为万事大吉了。
位置回调这块,说白了就是「注册-回调-移除」三个步骤。但每个步骤都有细节,不注意就会出问题。嗯,你按我上面说的来,基本不会踩坑。