分類: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()); } } }