三十九、访问基于位置的服务

当前移动设备上的一个流行功能是 GPS 功能,因此该设备可以告诉您在任何时间点的位置。虽然 GPS 服务最常见的用途是用于绘制地图和获取方向,但如果您知道自己的位置,还可以用它做其他事情。例如,您可以设置一个基于物理位置的动态聊天应用,这样用户就可以与离他们最近的人聊天。或者,你可以自动对 Twitter 或类似服务的帖子进行地理标记。

GPS 不是移动设备识别您位置的唯一方式。替代品包括以下几种:

  • 相当于 GPS 的欧洲版本,称为 Galileo,在撰写本文时仍在开发中
  • 手机信号塔三角测量,根据附近手机信号塔的信号强度来确定你的位置
  • 靠近已知地理位置的公共 Wi-Fi 热点

Android 设备可能拥有一项或多项这些服务。作为开发人员,您可以向设备询问您的位置,以及哪些供应器可用的详细信息。您甚至可以在模拟器中模拟您的位置,用于测试启用位置功能的应用。

位置提供者:他们知道你藏在哪里

Android 设备可以通过几种不同的方式来确定你的位置。有些会比其他的更准确。有些可能是免费的,而有些可能需要付费。有些可能不仅仅能告诉你你现在的位置,比如你的海拔高度或者你现在的速度。

Android 将所有这些抽象成了一组LocationProvider对象。您的 Android 环境将有零个或多个LocationProvider实例,每个实例用于设备上可用的不同定位服务。供应器不仅知道你的位置,还知道他们自己在准确性、成本等方面的特点。

作为一名开发人员,您将使用一个保存有LocationProvider集合的LocationManager,来计算哪个LocationProvider适合您的特定环境。您还需要应用中的权限,否则各种位置 API 将会由于安全违规而失败。根据您希望使用的位置供应器,您可能需要ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION,或者两者都需要(参见第 38 章)。

发现自我

对于定位服务来说,最明显的事情就是找出你现在的位置。为此,您首先需要获得一个LocationManager,因此从您的活动或服务中调用getSystemService(LOCATION_SERVICE),并将其转换为一个LocationManager。下一步是获得您想要使用的LocationProvider的名称。这里,您有两个主要选项:

  • 请用户选择一个供应器
  • 基于一组标准查找最匹配的供应器

如果你想让用户选择一个提供者,调用LocationManager上的getProviders()会给你一个提供者的List,然后你可以把它呈现给用户选择。

如果您想根据一组标准找到最匹配的提供者,创建并填充一个Criteria对象,说明您想从LocationProvider中得到什么的细节。以下是一些可用于指定标准的方法:

  • setAltitudeRequired():表示是否需要当前高度
  • setAccuracy():设置位置的最低精度等级,单位为米
  • setCostAllowed():控制供应器是否必须是免费的或者可以代表设备用户产生费用

给定一个已填充的Criteria对象,在你的LocationManager上调用getBestProvider(),Android 将筛选标准并给你最佳答案。请注意,并非您的所有标准都能得到满足;如果没有匹配,除了货币成本标准之外的所有标准都可以放宽。

您也可以使用一个LocationProvider名称(例如GPS_PROVIDER)进行硬连接,也许只是为了测试的目的。

一旦你知道了LocationProvider的名字,你就可以呼叫getLastKnownPosition()来找出你最近在哪里。然而,除非有其他原因导致期望的供应器收集定位(例如,除非 GPS 无线电打开),getLastKnownPosition()将返回null,指示没有已知位置。另一方面,getLastKnownPosition()不会产生金钱或电力成本,因为提供者不需要被激活来获取价值。

这些方法返回一个Location对象,它可以以 Java double的形式给出设备的纬度和经度,单位为度。如果特定位置供应器提供其他数据,您也可以获得:

  • 对于海拔高度,hasAltitude()将告诉您是否有海拔高度值,getAltitude()将以米为单位返回海拔高度。
  • 对于方位(即罗盘式的方向),hasBearing()将告诉您是否有可用的方位,getBearing()将返回正北以东的度数。
  • 对于速度,hasSpeed()会告诉你速度是否已知,getSpeed()会返回以米每秒为单位的速度。

不过,从LocationProvider获取Location的一个更可能的方法是注册更新,如下一节所述。

在移动中

不是所有的位置供应器都必须立即响应。例如,全球定位系统需要激活无线电,并从卫星获得定位,然后才能确定位置。这就是为什么 Android 不提供getMeMyCurrentLocationNow()方法。再加上你的用户可能不希望他们的移动反映在你的应用中,你最好注册位置更新,并以此作为获取当前位置的手段。

Internet/WeatherService/WeatherAPI示例应用展示了如何注册更新——在您的LocationManager实例上调用requestLocationUpdates()。这个方法有四个参数:

  • 您希望使用的位置提供者的名称
  • 在我们可能获得位置更新之前,应该过去多长时间(以毫秒为单位)
  • 在我们获得位置更新之前,设备必须移动多远(以米为单位)
  • 一个LocationListener,它将被通知关键的位置相关事件,如下例所示:

`LocationListener onLocationChange=new LocationListener() {   public void onLocationChanged(Location location) {     if (state.weather!=null) {       state.weather.getForecast(location, state);     }     else {       Log.w(getClass().getName(), "Unable to fetch forecast – no WeatherBinder");     }   }

public void onProviderDisabled(String provider) {     // required for interface, not used   }

public void onProviderEnabled(String provider) {     // required for interface, not used   }

public void onStatusChanged(String provider, int status,                                Bundle extras) {     // required for interface, not used   } }`

这里,我们所做的就是用提供给onLocationChanged()回调方法的Location触发一个FetchForecastTask

请记住,时间参数只是从功耗角度帮助控制 Android 的一个指南。你可能会得到比这更多的位置更新。要获得最大数量的位置更新,请为时间和距离约束提供0

当您不再需要更新时,用您注册的LocationListener呼叫removeUpdates()。如果你没有做到这一点,你的应用将继续接收位置更新,即使在所有的活动和类似的关闭,这也将阻止 Android 回收你的应用的内存。

还有另一个版本的requestLocationUpdates()采用了一个PendingIntent而不是一个LocationListener。如果您想在您的位置发生变化时得到通知,即使您的代码没有运行,这也很有用。例如,如果您正在记录移动,您可以使用一个PendingIntent来触发一个BroadcastReceiver ( getBroadcast())并让BroadcastReceiver将条目添加到日志中。这样,只有当位置改变时,代码才在内存中,所以当设备不移动时,不会占用系统资源。

我们到了吗?我们到了吗?

有时候,你对你现在在哪里不感兴趣,甚至对你什么时候搬家不感兴趣,而是想知道你什么时候到达你要去的地方。这可能是一个最终目的地,也可能是到达一组方向上的下一步,所以你可以给用户下一个指令。

为了实现这一点,LocationManager提供了addProximityAlert()。这记录了一个PendingIntent,当设备到达某个位置的某个距离内时,它将被触发。addProximityAlert()方法采用以下参数:

  • 感兴趣位置的纬度和经度。
  • 一个半径,指定您应该离Intent升起的位置有多近。
  • 注册的持续时间,以毫秒为单位。过了这段时间,注册自动失效。值-1表示注册持续到您通过removeProximityAlert()手动删除它。
  • 当设备在由位置和半径表示的目标区域内时要升高的PendingIntent

请注意,这并不保证您真的会收到一个Intent。定位服务可能会中断,或者在接近警报激活期间,设备可能不在目标区域内。例如,如果位置偏离一点,并且半径过小,设备可能只会绕过目标区域的边缘,或者它可能会快速通过目标区域,以至于在此期间设备的位置没有被采样。

您可以安排一个活动或接收者来响应您注册的Intent接近警报。当Intent到来时,你做什么取决于你自己。例如,您可以设置一个通知(例如,振动设备),将信息记录到内容供应器,或将消息发布到网站。请注意,无论何时对头寸进行采样并且您在目标区域内,您都会收到Intent,而不仅仅是在进入区域时。因此,根据目标区域的大小和设备移动的速度,您可能会得到几次Intent,也许是相当多次。

测试...测试...

Android 模拟器无法通过 GPS 定位,通过手机信号塔对你的位置进行三角测量,或者通过附近的 Wi-Fi 信号识别你的位置。因此,如果您想要模拟一个移动的设备,您将需要一些向模拟器提供模拟位置数据的方法。

不管出于什么原因,随着 Android 自身的发展,这一特定领域已经发生了重大变化。过去,您可以在应用中提供模拟位置数据,这对于演示非常方便。唉,这些选项在 Android 1.0 中都被删除了。

提供模拟位置数据的一个选项是 Dalvik 调试监控服务(DDMS)。这是一个独立于仿真器的外部程序,它可以以几种不同的格式为仿真器提供单个位置点或要遍历的完整路径。在您的清单文件ACCESS_MOCK_LOCATION中包含一个特定的权限,以允许访问数据。您还需要确保在模拟器的开发人员设置下启用了“允许模拟位置”选项。第 2 章中的“步骤 6:设置仪器”部分解释了如何访问这些设置。DDMS 本身在第 43 章中有更详细的描述。