5、位置监听:LocationListener接口、onLocationChanged回调、设置最小时间与距离间隔

好,咱们接着聊位置监听。

上一章我们把定位请求搭起来了,但光有请求还不够——你得知道位置什么时候更新了,对吧?这就像你叫了个外卖,不能干等着,得有个通知机制。Android 里这个通知机制,就是 LocationListener 接口。

LocationListener 是什么?说白了就是个回调接口

我个人习惯把 LocationListener 理解成一个「门铃」。你装好了门铃(注册监听),有人按门铃(位置更新),你就能知道谁来了(拿到新位置)。

这个接口里定义了四个方法,但最核心的只有一个——onLocationChanged()。其他几个方法,说句实话,我在大部分项目里都没怎么用过。但咱们还是得知道它们的存在,万一面试被问到呢?

方法 触发时机 使用频率
onLocationChanged(Location location) 位置发生变化时 ⭐⭐⭐⭐⭐
onStatusChanged(String provider, int status, Bundle extras) 定位提供者状态变化(已废弃)
onProviderEnabled(String provider) 定位提供者被启用 ⭐⭐
onProviderDisabled(String provider) 定位提供者被禁用 ⭐⭐
小提示:从 Android 11 开始,onStatusChanged() 已经被标记为废弃了。如果你还在用,建议尽快迁移到 LocationCallback 的方式。

onLocationChanged 回调——你真正要关心的东西

这个回调会传给你一个 Location 对象。嗯,这里要注意:这个对象可能为 null。我在项目中遇到过几次,明明定位成功了,但回调里拿到的 location 是空的。后来排查发现,是某些低端机在 GPS 信号弱的时候会返回空对象。

所以,拿到 location 之后第一件事——判空。

private LocationListener locationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        if (location == null) {
            // 别急着往下走,先处理空的情况
            Log.w("LocationDemo", "收到空位置,忽略本次回调");
            return;
        }
        
        double latitude = location.getLatitude();
        double longitude = location.getLongitude();
        float accuracy = location.getAccuracy();
        long time = location.getTime();
        
        // 更新UI或者上传服务器
        updateLocationUI(latitude, longitude, accuracy);
    }

    @Override
    public void onProviderDisabled(String provider) {
        // 用户关掉了GPS,可以弹个提示
        Toast.makeText(context, "请打开位置开关", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onProviderEnabled(String provider) {
        // 用户打开了GPS,可以重新请求位置
        requestLocationUpdates();
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        // 这个方法基本用不到了,保留空实现即可
    }
};
警告:千万不要在 onLocationChanged() 里做耗时操作!这个回调是在 Binder 线程池里执行的,你如果在这里做网络请求或者数据库写入,轻则卡顿,重则 ANR。我曾经见过一个同事直接在回调里写文件,结果定位一频繁,App 直接无响应了。

设置最小时间与距离间隔——别让手机累着

你想想看,如果每秒钟都回调一次,你的 App 会怎样?

电池哗哗地掉,CPU 呼呼地转,用户可能直接就把你 App 卸载了。所以,我们需要给定位监听加上「节流阀」。

这个节流阀就是 LocationRequest 里的两个参数:

  • setInterval(long millis):最小时间间隔,单位毫秒。比如设为 10000,就是至少 10 秒才回调一次。
  • setSmallestDisplacement(float meters):最小位移间隔,单位米。比如设为 50,就是位置变化超过 50 米才回调。

这两个参数可以单独用,也可以组合用。我个人的经验是:两个都设上,效果最好

LocationRequest locationRequest = LocationRequest.create()
        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
        .setInterval(10000)          // 10秒
        .setFastestInterval(5000)    // 最快5秒(系统可能比setInterval更快)
        .setSmallestDisplacement(50); // 50米

这里有个细节要注意:setInterval 是「建议值」,系统不一定会严格遵守。比如你设了 10 秒,但其他 App 也在请求定位,系统可能会合并回调,让你更频繁地收到更新。所以还有个 setFastestInterval,用来限制最快速度。

核心要点:
  • setInterval 是「我希望多久收到一次」
  • setFastestInterval 是「我最多能接受多快」
  • setSmallestDisplacement 是「位置变化多大才通知我」

注册与注销监听——有借有还,再借不难

注册监听很简单,调用 requestLocationUpdates() 就行。但别忘了,在不需要的时候一定要注销。我曾经犯过一个错:在 Activity 的 onCreate() 里注册了监听,但忘了在 onDestroy() 里注销。结果 Activity 销毁了,监听还在,导致内存泄漏。

// 注册
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(locationRequest, locationListener, Looper.getMainLooper());

// 注销(一定要在 onDestroy 或 onPause 里调用)
@Override
protected void onDestroy() {
    super.onDestroy();
    if (locationManager != null) {
        locationManager.removeUpdates(locationListener);
    }
}

第三个参数 Looper.getMainLooper() 表示回调在主线程执行。如果你不想阻塞主线程,也可以传 null,这样回调会在一个后台线程执行,但更新 UI 的时候记得切回主线程。

知识体系一览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个「定位监听速查表」:

位置监听核心流程 创建 LocationRequest 设置时间/距离间隔 实现 LocationListener 重写 onLocationChanged 注册监听 requestLocationUpdates onLocationChanged 回调处理 判空处理 获取经纬度/精度 更新UI或上传 ⚠ 务必在 onDestroy 中注销监听
我的习惯:在开发阶段,我会把 setInterval 设得短一些(比如 3 秒),方便测试。上线前再改成 10 秒或更长。另外,如果 App 在后台不需要定位,记得在 onPause() 里注销监听,在 onResume() 里重新注册——这样既省电又不会漏掉关键位置。

嗯,关于位置监听,核心就是这些。说白了就是三步:配好参数、实现回调、记得注销。你把这个流程走顺了,后面学 LocationCallbackFusedLocationProviderClient 就会轻松很多。


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