知道用户的位置信息可以使你的程序更加的智能并且能够提供更好的信息给你的用户,当开发一个位置感知的程序的时候,你可以使用gps或者是android的网络位置提供者来获取用户的位置。虽然使用GPS最精确,但是他仅能工作在户外,并且他更耗电量,还不能及时的返回用户的位置信息。相比较而言,android的网络位置提供者通过基站或者是WI-FI信号来判断用户的位置,它既能工作在户外,也能工作在室内,反应迅速,耗电较少。你可以同时使用这两种方式来获得位置信息,也可以使用这两种方式中的一种。
通过用户的移动设备获取位置信息是很复杂的,有这么几个原因导致获取位置时发生错误。
原因如下:
-
-
- 有多种方式获取位置,可以通过GPS,Cell-ID, Wi-Fi等等,这三种方式都能让你获取位置信息,判断使用哪种方式获得的信息会牵扯到精度,响应速度和电量耗费等等问题。
- 用户不断的移动,那么你必须不断的去估算用户的位置信息
- 不断变化的精度 从一个位置源获取的位置信息并不是一成不变的,10秒钟前你在A位置源获取的位置信息可能比你从A 处或者是B处获取的最新的位置信息要精确。
-
1. 请求位置更新
在定位上述的一些问题之前,让我们先来看看如何在android上获取位置信息。
在Android上通过回调的方式来获取用户位置信息,通过LocationManager的requestLocationUpdates(),方法,你就可以注册当前的Activity给LocationManager,那么这个LocationManager就能够周期性的通知这个已经注册了的activity最新的位置信息,这个activity必须提供监听器以让LocationManager对调,提供的监听器必须要实现LocationListener的如下几个方法,当用户的位置或者是网络位置服务的状态发生改变时,这几个方法就会被LocationManager回调。
// Acquire a reference to the system Location Manager
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the network location provider.
makeUseOfNewLocation(location);
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
引申:让我们来看看上述代码用到的相关的api
LocationManager: 这个类让你能够使用系统的位置服务,这些服务能够让android程序周期性的获得设备的物理位置的更新,或者是 当用户接近一个特别的物理位置的时候,应用程序能够发送一个特殊的意图提醒用户。你不能实例化这个类获得该类的实例对象,而是通过
Context.getSystemService(Context.LOCATION_SERVICE)来获取句柄
public void requestLocationUpdates (String provider, long minTime, float minDistance, LocationListener listener)
注册当前的activity能够被指定的provider周期性的通知。如果当前的位置或者是指定provider的状态发生变化的时候,当前activity提供的LocationListener会被周期性的调用。获取最新的位置信息需要消耗一些时间,应用程序可以通过
getLastKnownLocation(String)
方法得到一个最接近的位置(这个地方还有待斟酌)。如果当前provider服务被用户关闭的话,注册更新就会停止,provider的状态就会变为disable,
onProviderDisabled(String)方法就会被调用。一旦当前provider又被打开,那么
onProviderEnabled就会被调用,注册更新又会重新开始。
通过第二个和第三个参数能够控制位置更新的频率。第一个参数minTime代表两次位置更新之间的时间间隔,如果minTime大于0,那么LocationManager就会在一次更新完后,休息minTime时间,然后继续更新,这样能够节省电量。第三个参数minDistance是一个位置更新的条件,因为用户的位置可能是不断变化的,当前后两次位置的距离大于minDistance的时候,才会更新。如果想尽快得到获取更新,那么把这两个参数都设为0吧。
当保持GPS或者是无线网络服务一直运行的时候,后台的服务应当设置充分的时间间隔以至于设备不会消耗太多的电量,小于60000毫秒的时间间隔是不被推荐的。
调用位置服务的线程必须是含有looper的线程。
Location android.location.LocationManager.getLastKnownLocation(String provider)
从指定provider上获取最后一个位置修正的信息,即时不开启这个provider也能使用这个方法,但是这个时候这个provider的状态为disable,该函数的返回值为null,注意这个返回值location有可能国企,例如关闭移动设备或者是移动到其他的位置。
问题:
1. 系统是如何缓存位置修正信息的?
getLastKnownLocation方法获取的是最后一个缓存的位置信息,当调用requestLocationUpdates方法的时候,位置服务就会开始不断的缓存更新的位置修正信息,当调用了removeUpdates方法停止更新的时候,缓存就会停止,这个时候调用getLastKnownLocation方法,获取的就是最后一个缓存的位置修正信息。由于缓存的位置信息有可能过期,所以你需要判断返回值是否为null,并作特殊处理。
2. 在手机上如何开启位置服务呢?
以我的G3为例,打开“设置”-----》“位置“,就会看到如下界面
第一个对应NETWORK_PROVIDER
第二个对应GPS_PROVIDER
如果这里不打开服务的话,你的程序是无法获取位置信息的。
2. 位置服务的权限设置
为了使用NETWORK_PROVIDER or GPS_PROVIDER来获取位置信息,你必须要在程序中配置用户的权限,获取用户的位置信息需要权限ACCESS_COARSE_LOCATION 或者ACCESS_FINE_LOCATION
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
注意:如果你使用NETWORK_PROVIDER 和GPS_PROVIDER 两种方式的话,你只需要声明ACCESS_FINE_LOCATION这个权限就可以了,它包括了这两种provider所需要的权限。ACCESS_COARSE_LOCATION权限仅仅声明了NETWORK_PROVIDER所需要的权限
3. 定义一个获取用户配置信息的最好的模型
一个基本的获取获取位置信息的程序是简单的,但是如果要考虑到位置信息的精度,用户的移动,获取位置的方法,对于手机电量的需求,获取位置是很复杂的。
你必须建立一个模型来指导你的程序如何去获得用户位置,这个模型演示了什么时候监听和移除监听位置更新,什么时候缓存位置信息数据
获取用户位置信息的步骤
- 开启程序
- 开始监听位置的更新
- 确定一个最好的当前的位置信息
- 停止监听
- 使用最好的位置信息
4. 具体实现步骤
4.1 确定什么时间开始监听更新
你可能在程序启动启动起来之后就立即开始监听位置更新,或者是在用户某个动作之后监听。你需要知道的是长时间的监听位置信息的修正很快消耗掉很多电量,但是短时间内又无法获得充分精确地信息
LocationProvider locationProvider = LocationManager.NETWORK_PROVIDER;
// Or, use GPS location data:
// LocationProvider locationProvider = LocationManager.GPS_PROVIDER;
locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
4.2 使用上一个已知的位置来得到一个快速的位置修正
LocationListener获取第一个位置修正的时间会很长,为了避免用户等待,在你的LocationListener获取到一个更加精确的位置之前,你应当使用getLastKnownLocation(String)获取一个缓存的位置。
LocationProvider locationProvider = LocationManager.NETWORK_PROVIDER;
// Or use LocationManager.GPS_PROVIDER
Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
4.3 确定什么时候停止监听位置更新
确定什么时候那些新的位置修正不在需要的逻辑是简单还是复杂取决于你的应用程序。位置信息被得到的时间和位置信息被使用的时间之间的间隔越短,位置预估的精度就越高。时刻要注意,长时间的监听位置更新会消耗大量电量。一旦得到你所需要的信息之后,你应当立即调用removeUpdates(PendingIntent)方法停止监听。
// Remove the listener you previously added
locationManager.removeUpdates(locationListener);注意:这个方法必须调用,如果不调用这个方法,即时你退出了程序,仍然会被更新。
4.4 确定一个最精确的当前位置信息
你可能认为最新的位置修正时最精确的,然而因为位置修正的精度各种各样,最新的位置修正也不一定是最好的。你应当引入一个逻辑来根据一些条件来选择最好的位置修正。这些条件取决于你的程序
下面是一些你可以用来验证位置修正的步骤
验证是否获得的位置是否明显的比上一个要新
验证这个位置的精度比上一个位置的精度高还是低
验证这个位置那个provider提供的并且判断这个provider是否可信
下面就是一个详尽的判断的例子:
private static final int TWO_MINUTES = 1000 * 60 * 2;
/** Determines whether one Location reading is better than the current Location fix
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}