3、获取位置:LocationManager基本用法、getLastKnownLocation详解、requestLocationUpdates回调机制
说实话,LocationManager 这玩意儿,是 Android 定位的老祖宗了。虽然现在 Google 推 FusedLocationProvider,但很多老项目、车机、或者对 Google Play 服务有依赖限制的场景,还是得靠它。我个人习惯是,新项目用 Fused,但遇到兼容性需求,回头用 LocationManager 反而更稳。
这一章,咱们就把它掰开揉碎了讲。你想想看,一个 App 要拿到用户的位置,无非两种方式:要么问系统「你最近一次知道的位置在哪」,要么让系统「有位置更新了你就告诉我」。前者是 getLastKnownLocation,后者是 requestLocationUpdates。咱们一个一个来。
3.1 LocationManager 基本用法
要拿到 LocationManager,得先通过系统服务获取。代码很简单:
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
嗯,这里要注意。拿到这个对象之后,你才能去操作位置相关的功能。它支持多种 Provider,常见的有:
| Provider 名称 | 说明 | 典型精度 |
|---|---|---|
| GPS_PROVIDER | 卫星定位,耗电高,精度高 | 3-10 米 |
| NETWORK_PROVIDER | 基站/WiFi 定位,省电,精度一般 | 50-200 米 |
| PASSIVE_PROVIDER | 被动接收其他应用的位置更新 | 取决于来源 |
我在项目中遇到过,有些国产手机对 NETWORK_PROVIDER 支持很差,甚至直接返回 null。所以,我一般会先检查 Provider 是否可用:
boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
如果两个都不可用,那就得引导用户去设置里打开位置开关。别指望代码能硬开,Android 不允许。
3.2 getLastKnownLocation 详解
这个方法,说白了就是「查缓存」。系统会保存每个 Provider 最后一次获取到的位置。你调用它,就能拿到这个缓存值。
Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
但是!这里有个大坑。我曾经在一个户外导航 App 里,直接拿这个值当当前位置,结果用户反馈定位不准。为什么呢?因为 getLastKnownLocation 返回的位置可能是几分钟前、甚至几小时前的。如果用户刚从地铁站出来,这个位置可能还在地铁站里。
正确的做法是:
Location lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnown != null) {
long now = System.currentTimeMillis();
long age = now - lastKnown.getTime();
if (age < 2 * 60 * 1000) { // 2分钟以内
// 可以使用
} else {
// 太旧了,重新请求
}
}
另外,getLastKnownLocation 可能返回 null。尤其是刚开机、或者用户从未开启过定位时。所以,别指望它一定能拿到值。
3.3 requestLocationUpdates 回调机制
这才是实时定位的核心。你告诉系统:「每隔一段时间,或者每移动一段距离,你就通知我一次」。系统会通过回调把最新的 Location 对象传给你。
基本用法:
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, // 使用 GPS
5000, // 最小时间间隔,单位毫秒
10, // 最小距离变化,单位米
locationListener // 回调监听器
);
这里有两个参数要特别注意:
- minTime:5 秒。意思是至少 5 秒才回调一次。但注意,这不是精确的。系统可能会因为省电策略,把多个更新合并。
- minDistance:10 米。意思是位置变化超过 10 米才回调。如果设为 0,则只要有新位置就回调,非常耗电。
LocationListener 是一个接口,你需要实现它的四个方法:
LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 位置更新了!这里拿到最新的 location
double lat = location.getLatitude();
double lng = location.getLongitude();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Provider 状态变化,比如 GPS 信号丢失
}
@Override
public void onProviderEnabled(String provider) {
// 用户打开了定位
}
@Override
public void onProviderDisabled(String provider) {
// 用户关闭了定位
}
};
你想想看,onLocationChanged 是最常用的。但 onProviderDisabled 也很重要。我曾经有个 App,用户关闭 GPS 后,回调就停了,但 UI 上还显示着旧位置。后来我加了 onProviderDisabled 的处理,提示用户「定位已关闭」,体验就好多了。
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(locationListener);
}
3.4 知识体系图
下面这张图,帮你理清 LocationManager 的核心逻辑:
3.5 实际项目中的选择
我个人习惯是,如果 App 只需要一个大概位置(比如显示天气),用 getLastKnownLocation 就够了。但如果要做导航、运动轨迹记录,必须用 requestLocationUpdates。
而且,我建议你同时注册两个 Provider:先用 NETWORK_PROVIDER 快速拿到一个位置,等 GPS 锁定后,再切换到 GPS 的高精度数据。这样用户不会觉得「怎么等了 10 秒还没定位」。说白了,就是先用缓存顶一下,再用实时数据更新。
好了,这一章的内容就这些。LocationManager 虽然老,但基本功扎实。下一章咱们会聊聊更现代的 FusedLocationProvider,但如果你把 LocationManager 吃透了,理解 Fused 会轻松很多。