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 本身没有直接的错误回调方法,那怎么办?

你需要结合 FusedLocationProviderClientgetLastLocation 或者 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 知识体系图

下面这张图总结了位置回调的完整流程和关键节点。我建议你保存下来,写代码时对照着看。

位置回调核心流程 注册 LocationCallback requestLocationUpdates() onLocationResult() onLocationAvailability() 取最后一个位置 判断可用性 addOnFailureListener() 最后别忘了 removeLocationUpdates()

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 以上,后台位置权限需要单独申请。别只申请前台权限就以为万事大吉了。

位置回调这块,说白了就是「注册-回调-移除」三个步骤。但每个步骤都有细节,不注意就会出问题。嗯,你按我上面说的来,基本不会踩坑。

公众号:蓝海资料掘金营,微信deep3321