5、位置监听:LocationListener接口、onLocationChanged回调、设置最小时间与距离间隔
好,咱们接着聊位置监听。
上一章我们把定位请求搭起来了,但光有请求还不够——你得知道位置什么时候更新了,对吧?这就像你叫了个外卖,不能干等着,得有个通知机制。Android 里这个通知机制,就是 LocationListener 接口。
LocationListener 是什么?说白了就是个回调接口
我个人习惯把 LocationListener 理解成一个「门铃」。你装好了门铃(注册监听),有人按门铃(位置更新),你就能知道谁来了(拿到新位置)。
这个接口里定义了四个方法,但最核心的只有一个——onLocationChanged()。其他几个方法,说句实话,我在大部分项目里都没怎么用过。但咱们还是得知道它们的存在,万一面试被问到呢?
| 方法 | 触发时机 | 使用频率 |
|---|---|---|
onLocationChanged(Location location) |
位置发生变化时 | ⭐⭐⭐⭐⭐ |
onStatusChanged(String provider, int status, Bundle extras) |
定位提供者状态变化(已废弃) | ⭐ |
onProviderEnabled(String provider) |
定位提供者被启用 | ⭐⭐ |
onProviderDisabled(String provider) |
定位提供者被禁用 | ⭐⭐ |
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 的时候记得切回主线程。
知识体系一览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个「定位监听速查表」:
setInterval 设得短一些(比如 3 秒),方便测试。上线前再改成 10 秒或更长。另外,如果 App 在后台不需要定位,记得在 onPause() 里注销监听,在 onResume() 里重新注册——这样既省电又不会漏掉关键位置。
嗯,关于位置监听,核心就是这些。说白了就是三步:配好参数、实现回调、记得注销。你把这个流程走顺了,后面学 LocationCallback 和 FusedLocationProviderClient 就会轻松很多。