确定用户位置中的挑战
从移动设备获取用户位置可能很复杂。有几个原因可能会导致位置读数(不论来源)是否包含错误并且不准确。用户位置中的一些错误来源包括:
- 多种位置资源
GPS,Cell-ID和Wi-Fi都可以提供用户位置的线索。确定使用和信任是精确度,速度和电池效率的权衡问题。 - 用户移动
由于用户位置发生变化,您必须每隔一段时间重新估计一次用户的位置,以解决移动问题。 - 准确性的变化
来自每个位置源的位置估计值的准确性不一致。 10秒前从一个源获得的位置可能比来自另一个源或同一个源的最新位置更准确。
这些问题会使得难以获得可靠的用户位置阅读。 本文档提供的信息可帮助您应对这些挑战,以获得可靠的位置读数。 它还提供了一些想法,可以在您的应用程序中使用,为用户提供准确且快速响应的地理位置体验。
请求位置更新
在解决上述的一些位置错误之前,下面介绍如何在Android上获取用户位置。
在Android中获取用户位置通过回调方式工作。 可以通过调用requestLocationUpdates()从LocationManager接收位置更新,并向它传递一个LocationListener。 LocationListener必须实现LocationManager在用户位置更改或服务状态更改时调用的几种回调方法。
在Android 8.0(API级别26)及更高版本上,如果应用程序在请求当前位置时在后台运行,则该设备每小时仅计算几次位置。 要了解如何使您的应用适应这些计算限制,请参阅后台位置限制
以下代码显示了如何定义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()中的第一个参数是要使用的位置提供程序的类型(在这种情况下,网络位置提供程序用于手机信号塔和基于Wi-Fi的位置)。 您可以使用第二个和第三个参数控制侦听器接收更新的频率 - 第二个参数是通知之间的最短时间间隔,第三个参数是通知之间距离的最小更改 - 设置为零尽可能频繁地请求位置通知。 最后一个参数是您的LocationListener,它接收位置更新的回调。
要请求来自GPS提供商的位置更新,请使用GPS_PROVIDER而不是NETWORK_PROVIDER。 您还可以通过调用requestLocationUpdates()两次请求GPS和网络位置提供程序的位置更新 - 一次为NETWORK_PROVIDER,一次为GPS_PROVIDER
请求权限
为了从NETWORK_PROVIDER或GPS_PROVIDER接收位置更新,您必须分别在Android清单文件中声明ACCESS_COARSE_LOCATION或ACCESS_FINE_LOCATION权限来请求用户的许可。 没有这些权限,请求位置更新时,您的应用程序将在运行时失败。
如果您同时使用NETWORK_PROVIDER和GPS_PROVIDER,则只需请求ACCESS_FINE_LOCATION权限,因为它包含两个提供程序的权限。 ACCESS_COARSE_LOCATION的权限只允许访问NETWORK_PROVIDER。
警告:如果您的应用程序的目标是Android 5.0(API级别21)或更高,则必须声明您的应用程序使用清单文件中的android.hardware.location.network或android.hardware.location.gps硬件功能,具体取决于您的应用程序 应用程序从NETWORK_PROVIDER或从GPS_PROVIDER接收位置更新。 如果您的应用程序收到来自这些位置提供商来源的位置信息,则需要声明该应用程序在您的应用程序清单中使用了这些硬件功能。 在运行Android 5.0(API 21)之前的版本的设备上,请求ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限时会包含隐含的位置硬件功能请求。 但是,请求这些权限不会自动请求Android 5.0(API级别21)及更高级别上的位置硬件功能。
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
...
</manifest>
定义最佳性能模型
基于位置的应用程序现在已经很普遍,但由于准确性不高,用户移动,获取位置的多种方法以及节约电池的要求,获取用户位置非常复杂。 为了克服在保持电池电量的同时获得良好用户位置的障碍,您必须定义一个一致的模型,以指定应用程序如何获取用户位置。 此模型包括何时启动和停止侦听更新以及何时使用缓存的位置数据
用于获取用户位置的流程
以下是获取用户位置的典型流程:
- 启动应用
- 稍后,开始收听所需位置提供商的更新。
- 通过滤除新的但不太准确的修正来维持“当前最佳估计”位置。
- 停止监听位置更新。
- 利用最后的最佳位置估算。
图1在一个时间轴中演示了这个模型,该模型可视化了应用程序侦听位置更新的时间段以及在该时间段内发生的事件
图1在一个时间轴中演示了这个模型,该模型可视化应用程序侦听位置更新的时间段以及该时间段内发生的事件。
这种窗口模型 - 在接收位置更新期间 - 画出了在向应用程序添加基于位置的服务时需要做出的许多处理
决定何时开始收听更新
您可能希望在应用程序启动后立即开始侦听位置更新,或者仅在用户激活某个功能后才开始侦听位置更新。 请注意,侦听位置锁定的长时间窗口可能会消耗大量电池电量,但短时间段可能无法提供足够的准确度。
如上所示,您可以通过调用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);
决定何时停止监听更新
决定何时不再需要新的位置的程序的逻辑可能从简单到非常复杂,具体取决于您的应用程序。 获取位置和使用位置之间的短距离可以提高估计的准确性。 请务必注意,长时间监听会消耗大量电池电量,因此只要您有所需的信息,就应该通过调用removeUpdates(PendingIntent)来停止侦听更新:
// Remove the listener you previously added
locationManager.removeUpdates(locationListener);
保持当前的最佳位置
您可能预期最近的位置定位是最准确的。 但是,由于位置定位的准确性各不相同,因此最新定位并非总是最佳。 您应该包含根据多个条件选择位置修正的逻辑。 标准也取决于应用和现场测试的用例。
您可以采取以下几个步骤来验证定位精度:
- 检查检索的位置是否比以前的估计更新。
- 检查该位置声明的准确性是否比先前的估计更好或更差。
- 检查新位置来自哪个提供商,并确定您是否更加信任它。
这个逻辑的详细例子可以看起来像这样:
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);
}
调整模型以节省电池和数据交换
在您测试应用程序时,您可能会发现您提供良好位置和良好性能的模型需要进行一些调整。以下是您可能为了在两者之间找到平衡而改变的一些事情。
减小窗口的尺寸
您侦听位置更新的较小窗口意味着与GPS和网络位置服务的交互较少,从而保持电池寿命。但它也允许选择最佳估计的地点更少。
将位置提供程序设置为不频繁地返回更新
降低在窗口期间出现新更新的速率也可以提高电池效率,但是要以准确性为代价。 取舍的价值取决于您的应用程序的使用方式。 您可以通过增加requestLocationUpdates()中指定间隔时间和最小距离更改的参数来降低更新速率。
限制一组提供者
根据使用应用程序的环境或期望的准确性级别,您可以选择仅使用网络位置提供程序或仅使用GPS,而不使用两者。 只与其中一项服务进行交互,可能会降低电池使用的准确性。
常用应用案例
您可能想要在应用程序中获取用户位置的原因有很多。 以下是一些情景,您可以在其中使用用户位置来丰富您的应用程序。 每个场景还介绍了何时应该启动和停止监听位置的良好做法,以便获得良好的阅读效果并有助于延长电池寿命。
用位置标记用户创建的内容
您可能正在创建一个应用程序,其中用户创建的内容标有位置。 可以考虑用户分享他们的本地体验,发布餐厅评论,或者录制一些可以用当前位置进行增强的内容。 图2显示了这种相互作用可能发生的模型,与位置服务有关。
这与先前的代码中如何获取用户位置的模型(图1)一致。 为了获得最佳的位置准确性,您可以选择在用户开始创建内容或甚至应用程序启动时开始侦听位置更新,然后在内容准备好发布或记录时停止侦听更新。 您可能需要考虑创建内容的典型任务花费多长时间并判断此持续时间是否允许有效收集位置估计值。
帮助用户决定去哪里
您可能正在创建一个应用程序,试图为用户提供一组关于去哪里的选项。 例如,您希望提供附近餐馆,商店和娱乐的列表,建议顺序取决于用户的位置。
为了适应这样的流程,您可以选择:
- 在获得新的最佳估计值时重新排列建议
- 如果推荐顺序稳定,请停止收听更新
这种模型在图3中可视化。
提供模拟位置数据
在开发应用程序时,您肯定需要测试您的模型获取用户位置的效果。 这很容易使用真正的Android设备来完成。 但是,如果您没有设备,则仍然可以通过模拟Android模拟器中的位置数据来测试您的基于位置的功能。 您可以使用设备上开发人员选项中提供的模拟位置选项,或通过在模拟器控制台中使用geo命令,将模拟位置数据发送到您的应用程序。
使用开发者选项
在您的设备上,启用开发人员选项和USB调试,然后按照使用选择模拟位置应用程序选项的说明进行操作。
在模拟器控制台中使用geo命令
要从命令行发送模拟位置数据,请执行以下操作:
1.在Android模拟器中启动您的应用程序,并在SDK的/ tools目录中打开一个终端/控制台。
2.连接到仿真器控制台:
telnet localhost console-port
3.发送位置数据:
geo修复发送一个固定的地理位置。
该命令以十进制度数接受经度和纬度,并以米为单位选择一个可选高度。 例如:
geo fix -121.45356 46.51119 4392
geo nmea发送NMEA 0183句子。
该命令接受单个’
GPGGA′(修复数据)或′
G
P
G
G
A
′
(
修
复
数
据
)
或
′
GPRMC’(传输数据)类型的NMEA句子。 例如:
geo name $ GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E * 62
有关如何连接到仿真器控制台的信息,请参阅使用模拟器控制台