Android 位置服务
原文来自:http://developer.android.com/guide/topics/location/strategies.html
位置策略
注意:
本指南仅限android.location位置API。Google Play Services中的google位置API提供更强大更高级的框架,自动处理位置provider、用户移动和位置安全。它也依据电池消耗情况调整位置更新策略。在大多数情况下,使用Location Services API,你将获得更好的电池性能,更合适的精度。
更多关于Location Services API,请看:Google Location Services for Android.
知道用户在哪里能让你的app更智能同时传递更好的信息给用户。当开发一个android版的位置感知app,你可以使用GPS和android的网络位置provider来获取用户位置。尽管GPS更精确,但它只能在室外工作,快速消耗电池电量,而且不能及时地返回位置。android网络位置provider使用基站和Wi-Fi信号来决定用户的位置,不论是在室内还是室外都能提供位置信息,返回速度更快,使用更少的电池电量。为了在你的app中获取用户位置,你可以同时使用GPS和网络位置provider,或者只用一个。
决定用户位置的挑战
从移动设备获取用户位置可以很复杂。有几个原因为什么一次位置读取(不考虑数据源)会包含错误而且会不精确。用户位置错误的来源包括:
- 众多位置来源:GPS,基站和Wi-Fi每个都可以提供用户位置的线索。决定信任并使用哪个需要对精确度、速度和电池效率进行权衡。
- 用户移动:因为用户位置在变化,你必须通过经常性地重新评估用户位置来考虑移动的情况。
- 变化的精度:来自于各个数据源的位置估算在精度上并不一致。来自于一个数据源的10秒前的位置可能比来自于另一个或者同一个数据源获得的最新位置更精确。
这些问题会使得获取一个可信的用户位置读取很困难。本文提供信息来帮助你克服这些困难来获取一个可信的数据读取。它也提供这样一种想法,即你可以使用你的应用来为用户提供精确的、灵敏的地理位置体验。
请求位置更新
在定位上述提到的某些问题之前。这里有一段关于如何在android上获取用户位置的介绍。
在Android上获取用户位置是通过callback实现的。你表示你想要从 LocationManager通过调用requestLocationUpdates()来接收位置更新,传给一个LocationListener。你的LocationListener必须实现几个callback方法,当用户位置发生了变化或者当服务的状态发生了改变时Location Manager将会调用这些方法。
比如,下面的代码展示了怎样定义一个locationListener和请求位置更新:
// 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);
在requestLocationUpdates()中的第一个参数是位置provider的类型(在本例中,是基于基站和Wi-Fi的网络位置provider)。你可以通过第二个和第三个参数控制你的listener接受更新的频率——第二个是通知间的最小时间间隔,第三个是通知间的最小距离更改——都设置为0的意思是尽可能经常地请求位置通知。最后一个参数是你的locationListener,为位置更新接收callback。
从GPSprovider获取位置更新,需要将替换NETWORK_PROVIDER替换为GPS_PROVIDER。你也可以同时从GPS和网络位置请求位置更新,通过调用两次requestLocationUpdates()——一次用NETWORK_PROVIDER一次用GPS_PROVIDER。
请求用户许可
为了从NETWORK_PROVIDER或者GPS_PROVIDER接收位置更新,你必须请求用户许可,通过在Android manifest文件中声明ACCESS_COARSE_LOCATION 或者 ACCESS_FINE_LOCATION 许可。比如:
<manifest ... > <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> ... </manifest>
没有这些许可,你的app会在请求位置更新时运行时报错。
注意:
如果你使用了NETWORK_PROVIDER与 GPS_PROVIDER两者。那么你只需请求ACCESS_FINE_LOCATION 权限
,因为它包括了两个provider的权限。(ACCESS_COARSE_LOCATION 权限只包含NETWORK_PROVIDER的权限。 )
为最佳实践定义模型
基于位置的应用现在十分常见,但是因为小于最佳精度,用户移动,多种获取位置的方式,以及节约电池的原望,使得用户位置变得十分复杂。为了克服困难获取一个不错的用户位置同时又节约电池电量,你必须定义一个持久化模型,来指定你的app如何获取用户位置。这个模型包括你什么时候开始和结束监听更新以及什么时候使用缓存的位置数据。
获取用户位置的流程
这是获取用户位置的典型的过程流:
- 启动app
- 晚些时候,开启对想要的位置provider的监听
- 通过过滤新的但是低精度的值来定位结果,以获取位置的“当前最佳估值”
- 停止对位置更新的监听
- 利用最后一次最佳位置估值
图1 表示app监听位置更新的窗口的时间线
这个窗口模型——在位置更新收到的期间——列出了许多你需要做出的决定的框架,当你添加基于位置的服务到你的app时。
决定何时开始监听更新
你可能想要一启动你的app就开始监听位置更新,或者就在用户激活一个特定功能时。要知道长时间的窗口监听位置定位会消耗许多电池电量,但是短时间的可能不允许足够的精度。
正如前面演示的,你可以通过调用requestLocationUpdates()开始监听:
String locationProvider = LocationManager.NETWORK_PROVIDER; // Or, use GPS location data: // String locationProvider = LocationManager.GPS_PROVIDER; locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
通过上一次位置进行快速定位
通常情况下,你的位置监听获取第一次位置定位所花费的时间对于用户而言太漫长了。在一个更精确的位置被提供给你的位置监听之前,你应该通过调用getLastKnownLocation(String)来利用已缓存的位置:
String locationProvider = LocationManager.NETWORK_PROVIDER; // Or use LocationManager.GPS_PROVIDER Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
决定何时停止监听更新
决定何时不再需要新的定位的逻辑因你的app而异,可非常简单也可能非常复杂。位置获取和位置被使用之间的间隙提高了估值的精度。时刻记住,长时间监听消耗大量的电池电量,所以一旦你获得了想要的信息,你应该通过调用removeUpdate(PendingIntent)来停止监听:
// Remove the listener you previously added locationManager.removeUpdates(locationListener);
维护当前最佳估值
你可能希望最近一次定位是最精确的。然而,因为位置定位的精度是变化的,最近一次定位的结果不一定是最好的。你应该基于几个标准选择定位的逻辑。这些标准也因app与测试领域的用例而异。
这是验证位置定位精度的几个步骤:
- 检查位置检索是否明显地比上一次估值要新
- 检查位置所声明的精度是否比前一次估值要高或低
- 检查新的位置来自于哪个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 orse } 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); }
适应模型以节约电池与数据交换
在你测试你的 app时,你可能会发现你的模型在提供良好位置和良好性能方面需要一些调整。为了找到二者之间的良好平衡,这里是一些你可能需要更改的。
减少窗口的大小
监听位置更新的窗口更小,意味着更少地与GPS与网络位置服务交互,因此,延长了电池寿命。但是它也使得最佳估值可选择的位置更少。
设置位置provider不那么频繁地返回更新
在窗口期降低新的更新的出现频率能提高电池效率,但是会牺牲精度。权衡的值取决于你的app是怎样使用的。你可以通过增加requestLocationUpdates()中指定间隔时间和最小距离的参数的值来降低频率。
限制一系列provider
根据你的app的使用环境和期望的精度水平,你可能选择只是用网络位置provider或者只使用GPS,而不是两者。只与一个服务交互减少了电池用量,潜在的成本是精度。
常见的应用情况
有很多原因你可能想要在你的app中获取用户位置。以下是几个场景,你可以使用用户位置来丰富你的应用。每个场景也描述了什么时候你应该开始和结束位置监听以获得良好的读取和帮助延长电池使用时间的最佳实践。
用位置标记用户创建的内容
你可能在创建一个用户创建的内容以位置标记的app。想象一下用户共享他们的位置经历,发一个餐馆的评价,或者记录一些可以被他们的当前位置增强的内容。一个着眼于位置服务的关于这些交互可能怎样发生的模型,在图2中展现。
图2 展示用户用户消费当前位置时,位置获取、监听停止窗口的生命周期
与上一个用户位置怎么用代码获取的模型一致。为了最佳的位置精度,你可能选择在用户开始创建内容时或者当app启动时开始位置监听,然后当内容已经被发布或记录是停止监听。你可能需要考虑一个典型的创建内容的任务花费多长时间,判断这段时间是否可以得到位置估值的有效集合。
帮助用户决定去哪儿
你可能正在创建一个试图提供用户一系列关于去哪儿的选择的app。比如,你试图提供一系列附近餐馆、商店与娱乐场所,而且推荐结果由用户位置决定。
以适应这样一个流程,你可能会选择:
- 当一个新的最佳估值被获取时重排推荐
- 如果推荐的顺序稳定则停止监听更新
这种模型如图3所示:
图3 展示每当用户位置更新时数据的动态集合更新的窗口的生命周期
提供模拟位置数据
在你开发你的app时,你肯定需要测试你的模型获取用户位置工作的怎么样。使用真实的android设备最容易做到。然而,如果你没有设备,通过在android模拟器上模拟位置数据,你仍可测试你的基于位置的特性。有三种不同的方法来给你的app发送模拟位置数据:使用eclipse,DDMS,或者在emulator console使用“geo”命令。
注意:
提供模拟位置数据是作为GPS位置书注入的,所以为了使模拟位置数据工作,你必须从GPS_PROVIDER请求位置更新。
使用Eclipse
选择 Window > Show View > Other > Emulator Control。
在Emulator Control面板,在Location Controls下面输入GPS坐标作为单独的经度/维度坐标对,GPX文件用作路径回放,或者KML文件作为多个地标。(确保你在Devices面板选中了了设备——可以通过Window > Show View > Other > Devices打开。)
使用DDMS
使用DDMS工具,你可以用几种不同的方式模拟位置数据:
- 手动发送单个的经纬度坐标给设备
- 使用GPX文件描述路径的回放给设备
- 使用KML文件描述单个位置标记序列回放给设备
使用DDMS模仿位置数据的更多信息,查看 Using DDMS。
在emulator console使用“geo”命令
从命令行发送模拟数据:
- 在android模拟器登录你的app,在你的SDK或工具目录打开一个终端/控制台。
- 连接emulator console:
telnet localhost <console-port>
- 发送位置数据:
Geo fix 发送一个定位地理位置。
这个命令接受十进制角度经纬度,纬度以米为单位。比如:
geo fix -121.45356 46.51119 4392
Geo nmea 发送一个NMEA 0183 句子,类型'$GPGGA' (定位数据) 或者 '$GPRMC' (传输数据)。比如:
geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
更多关于如何连接到模拟器控制台的信息,查看Using the Emulator Console。