定位(Location) 和 传感器(Sensors)API充分发挥了移动设备的优势,您可以调用这些API,制作出交互性较高的应用程序,比如使用设备自带的GPS模块定位、或使用网络定位;使用加速器模块(accelerometer)监听设备的加速度、陀螺仪模块(gyroscope)监听设备偏转/倾斜时的转动角速度、温度计模块(temperature)测量当前设备所处环境的温度、气压计模块(barometer)测量当前设备所处环境的气压 等。
本文将介绍Android中地理定位的技术要点。在下一篇文章中,我将介绍有关传感器的相关知识,如需本文的访问官方原文,您可以点击这个链接:《Location and Sensors APIs》;
您也可以参考这些相关博文:
或这些Training:
定位策略(Location Strategies)
有关定位的API都在android.location包中。可以定位的应用程序已经屡见不鲜。您可以使用GPS 定位、或者Network Location Provider定位。GPS定位更加精准,但更加费电、且更适用于户外,而且不能立即返回设备所在的地理位置(Although GPS is most accurate, it only works outdoors, it quickly consumes battery power, and doesn’t return the location as quickly as users want);而Network Location Provider则是使用蜂窝网络或Wi-Fi网络定位,这种定位方式并不局限于户外,而且它的反应更加灵敏、费电较少。您可以根据实际情况选择选择哪一种定位方式(当然也可以同时使用两种定位方式)。
定位技术的实现难点(Challenges in Determining User Location)
获取设备的所在位置是一件复杂的事情(complicated),下面解释了为何在移动设备上定位会经常产生偏差:
- 定位方式的抉择(Multitude of location sources):GPS、Cell-ID、 Wi-Fi 等 都是定位的技术,为了决定应该使用哪一种方式(或哪几种方式)定位,您需要同时权衡精确性、定位速度、以及省电性 等多方面因素(a matter of trade-offs in accuracy, speed, and battery-efficiency)。
- 用户可能是不断移动的(User movement):由于用户的位置经常改变,您必须定时更新用户所在位置(you must account for movement by re-estimating user location every so often)。
- 定位的精确性也在变化(Varying accuracy):由于定位的结果可能来自不同的定位方式,而不同的方式精确性不同。某一种定位方式在10秒之前的定位结果可能比另一种方式在当前时刻的定位结果还要准确(A location obtained 10 seconds ago from one source might be more accurate than the newest location from another)。
上述几点导致了定位偶尔不准确的问题,下面将介绍如何克服这些问题。
请求位置更新(Requesting Location Updates)
在解决上述问题之前,我们先了解下如何在Android设备上获取当前的位置。
获取位置信息的方式是通过回调(callback)。您可以调用 requestLocationUpdates()
方法,并传入规定的参数(将在下面介绍),如LocationManager
类中的常量值表示选取哪一种方式进行定位,以及LocationListener
回调接口等参数。其中您需要实现LocationListener
接口中的方法,当设备的位置发生变化或定位方式发生改变时,相应的方法将被回调。示例如下所示:
// 获取LocationManager对象的引用
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// 实现LocationListener接口,并实现其中的方法
LocationListener locationListener = new LocationListener() {
//当设备的位置发生变化时,该方法将被回调
public void onLocationChanged(Location location) {
//利用回传的位置参数location定制逻辑
makeUseOfNewLocation(location);
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
// 调用requestLocationUpdates()方法注册位置监听
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
在requestLocationUpdates()方法中,参数1表示您使用的定位方式,(上例使用的是蜂窝网和Wi-Fi定位);参数2和参数3可以控制位置信息的更新频率:参数2表示获取位置信息的最小时间间隔(minimum time interval between notifications)、参数3表示获取位置信息的最小移动距离( the third is the minimum change in distance between notifications),若这两个参数都为0,表示尽可能频繁地获取位置信息(setting both to zero requests location notifications as frequently as possible);参数4是LocationListener回调接口,用于接收更新的位置等回调信息。
如果您需要使用GPS进行定位,那么可以把上例中的
LocationManager.NETWORK_PROVIDER
常量替换为LocationManager.GPS_PROVIDER
。如果您需要同时使用GPS和蜂窝网络/Wi-Fi定位,那么您可以调用requestLocationUpdates()
方法两次,一次传入LocationManager.NETWORK_PROVIDER
,一次传入LocationManager.GPS_PROVIDER
。
请求用户权限(Requesting User Permissions)
如使用
NETWORK_PROVIDER
(蜂窝网/Wi-Fi)定位,那么需要申请ACCESS_COARSE_LOCATION
权限;如使用GPS_PROVIDER
(GPS)定位,那么需要申请ACCESS_FINE_LOCATION
权限。如:
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
定义一个最佳的定位模型(Defining a Model for the Best Performance)
为了解决上面所述的若干问题,您需要设计一个在良好电量续航的前提下实现相对精确定位的模型。这个模型的设计要点是:在何时需要使用监听的形式获得位置信息、在何时需要使用缓存的位置信息。(when you start and stop listening for updates and when to use cached location data.)
获取位置信息的步骤(Flow for obtaining user location)
- 打开应用(Start application);
- 开启定位监听,有时候可以稍晚些时候开启(Sometime later, start listening for updates from desired location providers.)
- 保证获取的位置信息是最新的、但不必十分精确(Maintain a “current best estimate” of location by filtering out new, but less accurate fixes.)
- 停止监听位置信息更新(Stop listening for location updates)
- 使用最新的位置信息估计值定制逻辑(Take advantage of the last best location estimate)
上图是一个时间轴,它表示上述步骤按时间的执行顺序,即应用程序监听及更新位置信息的过程,这也是一般包含定位功能的应用程序的定位模型。
确定何时开始监听位置信息的更新(Deciding when to start listening for updates)
您可能希望在程序一启动就开始监听位置的实时变化,或者仅仅是某个功能需要得到位置信息时才启动定位功能。需要注意的是,长时间的开启定位功能是非常耗电的,而在短时间内开启定位功能又不能保证定位的精确性。
一般情况下,调用requestLocationUpdates()
方法就意味着定位功能已开启:
String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or, use GPS location data:
// String locationProvider = LocationManager.GPS_PROVIDER;
locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
使用最新的位置信息快速定位(Getting a fast fix with the last known location)
使用监听器首次获取实时位置信息经常比较耗时(The time it takes for your location listener to receive the first location fix is often too long for users wait),在监听器返回精确的定位信息之前,您需要调用
getLastKnownLocation(String)
方法获取缓存的位置信息(Until a more accurate location is provided to your location listener, you should utilize a cached location by calling getLastKnownLocation(String)
):
String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or use LocationManager.GPS_PROVIDER
Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
确定何时停止监听位置信息的更新(Deciding when to stop listening for updates)
在监听位置信息时,您应当时刻考虑到好点的情况,当获取到位置信息后,应当调用
removeUpdates(PendingIntent)
方法停止监听位置信息:
// Remove the listener you previously added
locationManager.removeUpdates(locationListener);
保证持有当前实时位置的最佳估计值(Maintaining a current best estimate)
您可能会认为监听到最新的位置信息都是最精确的。但事实并非如此——因为定位的方式和所处环境不尽相同。您需要根据不同的场景和情况切换定位的模式。
您可以通过下面的步骤来检测得到的当前位置信息是否准确:
- 检查当前获取的位置信息是否跟之前的位置有显著的变化;
- 检查当前的定位方式是否比之前的定位方式精确性更高;
- 检查在当前环境下,哪一种定位方式可以进行更加精确的定位;
示例如下:
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);
}
在省电和精确定位之间作出权衡(Adjusting the model to save battery and data exchange)
- 缩减监听位置的生命周期(Reduce the size of the window):这样可以减少GPS等模块的调用,即延长电池续航,但这样可能不会得到精确的位置信息;
- 减少监听方法的回调频率(Set the location providers to return updates less frequently):这样同样可以省电,但是以定位精确性作为代价的,您可以在
requestLocationUpdates()
方法的第二个和第三个参数进行设置(增大它们的值); - 限制同时使用多种定位方式(Restrict a set of providers):您可以根据实际情况,仅使用某一种定位方式即可,这样同样可以增加续航时间。
时间: 2024-10-07 13:44:47