前言:
HI,欢迎来到裴智飞的《每周一博》。从九月开始,我将继续每周更新一篇博客,配合不定期更新的喜马拉雅,让自己一直保持学习的状态。今天是九月第一周,我先给大家分享一些和定位相关的知识。
Android提供了定位服务和地理编码服务,它们分别是由LocationManager和Geocoder来提供的,下面我来分别介绍这2部分内容。
一. LocationManager:
LocationManager系统服务是位置服务的核心组件,它提供了一系列方法来处理与位置相关的问题,比如查询上一个已知位置,定期更新设备的地理位置,或者当设备进入给定地理位置附近时,触发应用指定意图等;
(A). 使用LocationManager需要以下过程;
1.获取LocationManager,它不能直接实例化:
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
2.了解LocationProvider:它是位置信息提供者,系统一般提供三种方式获取地理位置信息:
(1)GPS_PROVIDER:通过 GPS 来获取地理位置的经纬度信息;
优点:获取地理位置信息精确度高;
缺点:只能在户外使用,获取经纬度信息耗时,耗电;
(2)NETWORK_PROVIDER:通过移动网络的基站或者 Wi-Fi 来获取地理位置;
优点:只要有网络,就可以快速定位,室内室外都可;
缺点:精确度不高;
(3)PASSIVE_PROVIDER:被动接收更新地理位置信息,而不用自己请求地理位置信息。 PASSIVE_PROVIDER 返回的位置是通过其他 providers 产生的,可以查询 getProvider() 方法决定位置更新的由来,需要 ACCESS_FINE_LOCATION 权限,但是如果未启用 GPS,则此 provider 可能只返回粗略位置匹配;
获取provider的方法有getProviders,getAllProviders,getBestProvider(根据一组条件来返回合适的provider)
List<String> list = locationManager.getProviders(true);
if (list != null) {
for (String x : list) {
Log.e("gzq", "name:" + x);
}
}
LocationProvider lpGps = locationManager.getProvider(LocationManager.GPS_PROVIDER);
LocationProvider lpNet = locationManager.getProvider(LocationManager.NETWORK_PROVIDER);
LocationProvider lpPsv = locationManager.getProvider(LocationManager.PASSIVE_PROVIDER);
Criteria criteria = new Criteria();
// Criteria是一组筛选条件
criteria.setAccuracy(Criteria.ACCURACY_FINE);
//设置定位精准度
criteria.setAltitudeRequired(false);
//是否要求海拔
criteria.setBearingRequired(true);
//是否要求方向
criteria.setCostAllowed(true);
//是否要求收费
criteria.setSpeedRequired(true);
//是否要求速度
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
//设置电池耗电要求
criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
//设置方向精确度
criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
//设置速度精确度
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
//设置水平方向精确度
criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
//设置垂直方向精确度
//返回满足条件的当前设备可用的provider,第二个参数为false时返回当前设备所有provider中最符合条件的那个provider,但是不一定可用
String mProvider = locationManager.getBestProvider(criteria, true);
if (mProvider != null) {
Log.e("gzq", "mProvider:" + mProvider);
}
}
打印出来的结果就是passive,gps,network;
3.声明权限
(1)ACCESS_FINE_LOCATION是精确位置,如果使用GPS_PROVIDER或者同时使用GPS_PROVIDER和NETWORK_PROVIDER,需声明该权限,它对于这两个provider都是有效的;
(2)ACCESS_COARSE_LOCATION是粗略位置,该权限只针对NETWORK_PROVIDER。
4.注册一个位置监听器来接受结果
private final class MyLocationListener implements LocationListener{
public void onLocationChanged(Location location) {
Log.e("gzq", "onLocationChanged" + location.toString());
}
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.e("gzq", "onStatusChanged" + status);
}
public void onProviderEnabled(String provider) {
Log.e("gzq", "onProviderEnabled");
}
public void onProviderDisabled(String provider) {
Log.e("gzq", "onProviderDisabled");
}
}
这个回调里面有4个方法;
(1)onLocationChanged:当位置发生改变后就会回调该方法,经纬度相关信息存在Location里面;
(2)onStatusChanged:我们所采用的provider状态改变时会回调,该状态有3种;
LocationProvider.OUT_OF_SERVICE = 0:无服务
LocationProvider.AVAILABLE = 2:provider可用
LocationProvider.TEMPORARILY_UNAVAILABLE = 1:provider不可用
(3)onProviderEnabled:当provider可用时被触发,比如定位模式切换到了使用精确位置时GPSProvider就会回调该方法;
(4)onProviderDisabled:当provider不可用时被触发,比如定位模式切换到了使用使用网络定位时GPSProvider就会回调该方法;
5.获取位置信息,调用监听方法,不过在获取位置前先判断一下要调用的provider是否可用;
if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5,10, locationListener);
}
这个方法表明要跟踪GPS位置的变化,并且每5秒刷新一次,同时两次的位置的间隔要超过10米;
在不需要位置的时候解注册监听:
locationManager.removeUpdates(locationListener);
关于requestLocationUpdates有一些问题需要注意:
(1)刚才我们传入的是Listener,其实也可以用PendingIntent来代替Listener,当位置更新时会通过广播回调,使用2个键KEY_LOCATION_CHANGED和Location来接收位置变化;
(2)参数这里里可以传递一个Looper,如果不指定的话,则调用线程必须已经是一个Looper线程,比如调用Activity的主线程,如果指定了Looper,则在提供的Looper线程上进行回调;
(B). LocationManager还提供了一些其他有用的方法:
1.获取缓存中的位置信息getLastKnownLocation
Location location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
该方法不会发起监听,返回的是上一次的位置信息,但此前如果没有位置更新的话,返回的位置信息可能是错误的;
2.获取一次定位结果requestSingleUpdate
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER,locationListener,null);
如果不想一直监听位置信息,那么可以用requestSingleUpdate来实现只请求一次定位,该方法也要在主线程上执行;
3.发送辅助指令sendExtraCommand
mLocationManager.sendExtraCommand("LOC","NOTIFY_DOWNLOAD", extras);
可以通过Bundle参数来发送相关信息;
4.判断provider是否可用
mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
5.添加地理围栏
locationManager.addProximityAlert(38.234, 114.234, 5, -1, PendingIntent.getBroadcast(this, 1, new Intent(), 3));
可以设置一个区域,当进入或离开这个区域的时候会收到通知,前两个参数指定一个点,第三个参数是半径,第四个参数是超时时间,设置为-1表示不存在超时,最后一个是广播接收器。
触发的Intent将使用键KEY_PROXIMITY_ENTERING,如果值为true,则设备进入邻近区域,如果是false,说明设备离开该区域。
二. Geocoder:
上面介绍了LocationManager的使用,下面介绍下地理编码的使用。
Geocoder 用于处理地理编码和反地理编码,地理编码是将街道地址或其他地理位置变换为经纬度的过程。逆向地理编码是将经纬度转换为地址的过程。反地理编码位置描述中的细节数量可能会有所不同,例如可能包含最近建筑物的完整街道地址,而另一个可能只包含城市名称和邮政编码。
Geocoder 类需要一个未包含在核心Android框架中的服务。如果平台中没有该服务,Geocoder查询方法将返回一个空列表。使用 isPresent() 方法可以来确定Geocoder实现是否存在。由于国内用不了GoogleServices 服务,因此一般的手机厂商都会在自己的手机内内置百度或高德地图服务。
主要方法有
1.isPresent():判断当前设备是否内置了地理位置服务,返回 true 表示地理编码可以使用,false为不可使用;
2.getFromLocation():根据经纬度返回对应的地理位置信息,参数maxResults表示返回地址的数目,建议使用1-5;
Geocoder geocoder = new Geocoder(this);
boolean flag = Geocoder.isPresent();
if (flag) {
try {
List<Address> addresses = geocoder.getFromLocation(39.345345, 116.345, 1);
if (addresses.size() > 0) {
Address address = addresses.get(0);
String sAddress;
if (!TextUtils.isEmpty(address.getLocality())) {
if (!TextUtils.isEmpty(address.getFeatureName())) {
//市和周边地址
sAddress = address.getLocality() + " " + address.getFeatureName();
} else {
sAddress = address.getLocality();
}
} else {
sAddress = "定位失败";
}
Log.e("gzq", "sAddress:" + sAddress);
}
} catch (IOException e) {
}
}
该方法返回一个List,从中取出第一个元素,如果有locality(地址位置)属性,并且有featureName(地址要素),就可以去取相关信息,比如国家(countryName),邮编(postalCode),国家编码(countryCode),省份(adminArea),二级省份(subAdminArea),二级城市(subLocality),道路(thoroughfare)等;
3.getFromLocationName():返回描述地理位置信息的集合,maxResults是返回地址的数目,建议使用1-5;
List<Address> addresses = geocoder.getFromLocationName("西二旗", 1);
if (addresses.size() > 0) {
//返回当前位置,精度可调
Address address = addresses.get(0);
if (address != null) {
Log.e("gzq", "sAddress:" + address.getLatitude());
Log.e("gzq", "sAddress:" + address.getLongitude());
}
}
Geocoder的使用比较简单,getFromLocation和getFromLocationName都可以在线程里面执行。
结尾:
本周给大家分享了客户端应该如何使用定位相关服务,主要就是使用LocationManger和Geocoder的API,下周我将走读LocationMangerService源码,详细分析定位服务系统是如何实现的,我们不仅要知道how,还要知道why,下周再见。