【Android】18.1 利用安卓内置的定位服务实现位置跟踪
分类:C#、Android、VS2015;
创建日期:2016-03-04
一、安卓内置的定位服务简介
通常将各种不同的定位技术称为位置服务或定位服务。这种服务是通过电信运营商的无线电通信网络(如GSM网、CSMA网等)或外部定位方式(如GPS)来实现的。
Android提供了对移动数据(cell tower,也叫蜂窝发射塔)、无线网络(Wi-fi)、全球定位系统(GPS)等多种定位技术的访问。
在安卓系统中,无论你选择哪种定位服务API获取位置数据,这些基本概念都是相同的。
Android平台提供的定位服务API主要包括以下几种。
1、LocationManager
定位管理器。用于获取和管理用户当前的位置,追踪设备的移动路线,或者设定敏感区域(在进入或离开敏感区域时设备会发出警报)。
2、Location Providers
LocationProviders:定位提供程序。提供定位功能的组件集合,集合中的每种组件都以不同的技术提供设备的当前位置,这些提供程序的区别在于定位的精度、速度和成本等方面。
Android收集位置数据是靠硬件来实现的,所用的硬件取决于位置提供程序的类型。
Android提供了三种类型的位置提供程序:
- GPS Provider – 该方式使用 GPS 和辅助GPS (aGPS) 返回 GPS 数据收集的蜂窝塔的组合。全球定位系统(GPS)最耗电,但它给出的位置也最准确,适合在户外使用。
- Network Provider – 该方式提供 WiFi 和蜂窝数据的组合,包括辅助GPS收集的数据。它比GPS Privider的耗电量少一些,但返回的位置精度没有GPS高。
- Passive Provider – 该方式由其他应用程序或服务来生成位置数据,由于不需要持续的位置更新,因此这种方式的耗电量最少,但该方式得到的位置数据不太可靠(可信性不高)。
另外,位置提供程序并不总是可用的。例如,你编写的应用程序希望使用GPS定位,但是手机的GPS可能处于关闭状态或者手机根本就没有获取GPS数据的硬件,这种情况下,实际上是无法使用GPS Privider的。
如果所要求的位置提供程序不可用,该提供程序返回null。
3、Location Permissions
凡是位置感知的应用程序,都需要访问设备的硬件传感器才能接收定位数据,包括GPS、Wi-fi或移动数据(蜂窝数据)。这些数据的访问权限都是通过AndroidManifest来配置的。
一共有两个基于硬件定位服务的数据访问权限,根据应用程序要求和所选择的API,你可以选择这两种的其中之一(一般不要两个都选):
- ACCESS_FINE_LOCATION – 该权限允许应用程序访问GPS。该权限可使用GPS Provider和Passive Provider(Passive Provider需要访问由另一个应用程序或服务收集的GPS数据的权限)。另外,根据需要,也可以选择Network Provider。
- ACCESS_COARSE_LOCATION – 如果未设置ACCESS_FINE_LOCATION,利用该权限可使用Network Provider访问移动电话和WiFi。
权限设置办法:在【解决方案资源管理器】中,双击项目的【Properties】文件夹,即可弹出该项目的属性窗口,在该窗口中,设置相应的访问权限即可。
再次提醒注意:设置ACCESS_FINE_LOCATION权限意味着已经拥有对Coarse和fine这两个位置数据的访问权限。无论什么情况,应用程序都应该仅使用最小的运行权限以节省手机的耗电量。或者说,不管你的应用程序实现什么功能,都没有必要“同时”设置ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION。
4、定位服务的基本设计思路
定位服务是一种特殊类型的由Android系统管理的服务。系统服务总是与设备的硬件进行交互,并总是处于运行状态。
在应用程序中,可利用ILocationListener与LocationManager类来访问定位服务。具体来说,要利用Android Location Service获取用户的位置,需要完成下面的步骤:
- 获取对LocationManager类的引用。
- 使用LocationManager请求提供程序更新位置
- 当位置发生改变时,在实现的ILocationListener接口中处理位置信息。
- 当应用程序转入后台时停止位置更新。
要在应用程序中获取位置更新的数据,需要先获取对LocationManager的引用,然后再调用RequestLocationUpdates()方法获取位置服务提供的更新数据。
二、利用Android内置的定位服务API实现定位跟踪
Android的定位服务是安卓系统提供的标准API。位置数据是通过硬件传感器(hardware sensors)和系统服务(system Service)来共同收集的。
1、获取LocationManager实例
LocationManager是一个特殊的类,利用它提供的方法可以与系统位置服务进行交互。下面的代码演示了如何在Activity中调用GetSystemService()方法获取对LocationManager的引用:
private LocationManager locMgr;
...
locMgr = GetSystemService(Context.LocationService) as LocationManager;
一般先将locMgr声明为字段,然后再在重写的OnCreate()方法中获取对LocationManager的引用。这样一来,就可以在Activity生命周期期间调用的不同方法中使用这个实例了。
2、请求位置更新
一旦得到了LocationManager的实例,就可以调用该实例的RequestionLocationUpdates()方法告诉它你希望开始接收位置更新的服务,包括需要什么类型的位置信息,以及希望接收位置数据的频率,更新频率用时间(单位:毫秒)和距离阈值(单位:米)等。
例如,下面的C#代码实现每2000毫秒发出一次定位请求,当位置变化超过1米时调用一次位置更新:
protected override void OnResume ()
{
base.OnResume ();
string Provider = LocationManager.GpsProvider;
if(locMgr.IsProviderEnabled(Provider))
{
locMgr.RequestLocationUpdates(Provider, 2000, 1, this);
}
else
{
Log.Info(tag, Provider + " 不可用,请检查设备的定位权限设置");
}
}
应用程序应该仅在需要时才请求位置更新,这会保留电池寿命并创建更好的用户体验。
3、获取位置信息
一旦应用程序得到了更新的数据,就可以实现 ILocationListener 接口,利用它接收来自定位服务的信息。此接口提供了用于监听位置服务和位置提供程序的方法。
下面的代码演示了如何在MainActivity中实现ILocationListener接口:
public class MainActivity : Activity, ILocationListener
{
...
public void OnProviderEnabled (string provider)
{
...
}
public void OnProviderDisabled (string provider)
{
...
}
public void OnStatusChanged (string provider, Availability status, Bundle extras)
{
...
}
public void OnLocationChanged (Android.Locations.Location location)
{
...
}
}
该接口允许我们用四个系统事件来检查提供程序的状态并获取位置信息:
- OnProviderEnabled 和 OnProviderDisabled - 通知应用程序用户启用或禁用了对应的提供程序(例如,用户可能会禁用 GPS 以节省电池寿命)。
- OnStatusChanged - 通知应用程序该提供程序的可用性以及对应的状态发生了更改(例如,用户在室内走的时候GPS可用性发生了更改)。
- OnLocationChanged - 当请求的更新位置发生变化时(什么时候请求更新与设置的位置更新参数有关),系统会自动调用OnLocationChanged方法。
下面的代码解释了如何在Activity中实现OnLocationChanged方法,并将接收的位置更新输出到屏幕上:
TextView latitude;
TextView longitude;
public void OnLocationChanged (Location location)
{
latitude.Text = "纬度(Latitude):" + location.Latitude;
longitude.Text = "经度(Longitude):" + location.Longitude;
}
4、停止位置更新
RemoveUpdates()方法告诉系统停止定位服务向应用程序发送更新。在重写的OnPause()方法中调用RemoveUpdates()的目的是为了节省电量消耗以延长电池的寿命:
protected override void OnPause ()
{
base.OnPause ();
locMgr.RemoveUpdates (this);
}
如果应用程序需要在后台获取位置更新,可创建一个自定义的服务,然后在该服务中使用定位服务。
5、GetBestProvider方法
前面介绍了如何用GPS作为位置提供程序。然而,GPS可能无法在所有情况下都可用,例如,如果该设备是在室内或者没有GPS接收硬件,此时提供程序将为null。
当GPS不可用时,如果想要应用程序仍然能继续工作,此时可以利用GetBestProvider()方法寻求已启动的最佳可用的设备而不是死板地只会去调用特定的提供程序。或者说,如果你希望实现的代码更简单,而不是自己去考虑和处理各种可能性,可利用GetBestProvider()方法让它帮你找到最佳的提供程序。
另外,还可以通过参数告诉GetBestProvider()方法对定位提供程序的要求(比如准确性和耗电量等),此时GetBestProvider()方法就会按给定的条件返回最佳的提供程序:
Criteria locationCriteria = new Criteria();
locationCriteria.Accuracy = Accuracy.Coarse;
locationCriteria.PowerRequirement = Power.Medium;
locationProvider = locMgr.GetBestProvider(locationCriteria, true);
if(locationProvider != null)
{
locMgr.RequestLocationUpdates (locationProvider, 2000, 1, this);
}
else
{
Log.Info(tag, "No location providers available");
}
注意:如果用户禁用了所有的定位服务,GetBestProvider()将返回null。
要查看此代码在实际设备上是如何工作的,一定要确保你的设备启用了GPS、Wi-fi、移动数据(蜂窝网络)。具体设置办法如下面的截图所示,其中左图是真实手机的定位模式设置(不同型号的手机设置界面可能不同),右图是Android 6.0模拟器的定位模式设置:
下面的屏幕截图演示了GetBestProvider()在真实手机上的运行效果(具体数据与你所在的位置有关):
记住:GetBestProvider()不会动态地更改提供程序,它仅在生命周期期间一次性地决定最佳的可用提供程序。
如果设置后提供程序的状态发生了变化,应用程序需要在ILocationListener接口要求实现的方法中添加额外的代码来处理它,即:在OnProviderEnabled()、OnProviderDisabled() 和 OnStatusChanged()方法中处理各种可能性。
三、示例1—安卓内置的定位服务基本用法
运行截图
在真实手机上运行该程序,才能看到实际的数据。在模拟器上,开始看到的是下面截图所示的界面,单击【启动定位服务】即可根据位置的变化更新所在位置,但是由于模拟器没有gps硬件传感器,因此显示的经纬度结果将为空。
设计步骤
1、设置权限
该例子需要下面的权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
设置办法:在【解决方案资源管理器】中双击项目的Properties文件夹,在弹出的窗口中查看是勾选了该权限,如果没勾选就勾选它。
2、添加ch1801Main.axml文件
在layout文件夹下添加该文件,模板选择【Layout】。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/myButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <TextView android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/latitude" /> <TextView android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/longitude" /> <TextView android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/provider" /> </LinearLayout>
3、添加ch1801MainActivity.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Acivity】。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using Android.Locations; using Android.Util; namespace MyDemos.SrcDemos { [Activity(Label = "【例18-1】安卓定位服务基本用法")] public class ch1801MainActivity : Activity, ILocationListener { private LocationManager locMgr; private string tag = "ch1801MainActivity"; private Button button; private TextView latitude; private TextView longitude; private TextView provider; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.ch1801Main); button = FindViewById<Button>(Resource.Id.myButton); latitude = FindViewById<TextView>(Resource.Id.latitude); longitude = FindViewById<TextView>(Resource.Id.longitude); provider = FindViewById<TextView>(Resource.Id.provider); } protected override void OnStart() { base.OnStart(); Log.Debug(tag, "OnStart called"); } // 由于每次启动Activity时都会调用OnResume()方法,所以将请求位置更新的代码放在此方法中 protected override void OnResume() { base.OnResume(); Log.Debug(tag, "OnResume called"); // 初始化定位管理器 locMgr = GetSystemService(Context.LocationService) as LocationManager; button.Click += delegate { button.Text = "定位服务正在运行"; var locationCriteria = new Criteria(); locationCriteria.Accuracy = Accuracy.Coarse; locationCriteria.PowerRequirement = Power.Medium; string locationProvider = locMgr.GetBestProvider(locationCriteria, true); Log.Debug(tag, "开始定位更新,使用的提供程序:" + locationProvider.ToString()); LocationProvider p = locMgr.GetProvider(locationProvider); provider.Text = "提供程序(Provider): " + locationProvider.ToString(); // 更新所需的最短时间minTime=2000毫秒,更新所需的最短移动距离minDistance=1米 locMgr.RequestLocationUpdates(locationProvider, 2000, 1, this); }; } protected override void OnPause() { base.OnPause(); Log.Debug(tag, "OnPause called"); // 当应用程序转入后台时,停止发送定位更新 // RemoveUpdates takes a pending intent - here, we pass the current Activity locMgr.RemoveUpdates(this); Log.Debug(tag, "程序转入后台,定位更新停止。"); } protected override void OnStop() { base.OnStop(); Log.Debug(tag, "OnStop called"); } public void OnLocationChanged(Location location) { Log.Debug(tag, "位置发生了变化"); latitude.Text = "纬度(Latitude): " + location.Latitude.ToString(); longitude.Text = "经度(Longitude): " + location.Longitude.ToString(); provider.Text = "提供程序(Provider): " + location.Provider.ToString(); } public void OnProviderDisabled(string provider) { Log.Debug(tag, provider + " disabled by user"); } public void OnProviderEnabled(string provider) { Log.Debug(tag, provider + " enabled by user"); } public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras) { Log.Debug(tag, provider + " availability has changed to " + status.ToString()); } } }