安卓定位服務API指南
安卓位置策略指南
注意:本指南中描述的策略僅僅適用於android.location中的定位API。 Google位置服務API是Google Play服務的一部分,提供了更強大的高階框架,可自動處理位置提供商、使用者移動和位置精確度。 它還根據您提供的功耗引數處理位置更新計劃。 在大多數情況下,您可以通過使用位置服務API獲得更好的電池效能以及更合適的精度。要詳細瞭解位置服務API,請參閱Android版Google定位服務。
目錄
文件
- 使用者定位服務的挑戰
- 如何向系統請求定位
- 根據情況選擇一個最優的定位策略
- 處理給定的虛擬定位資料(暫不翻譯)
關鍵類
1.使用者定位服務的挑戰
從移動裝置獲得使用者位置是極其複雜的,以下幾個原因會導致位置讀取的錯誤或不準確:
多種位置來源:GPS、小區ID和Wi-Fi可以各自提供使用者位置的線索,而確定使用哪個服務取決於精度、速度和電池效率的權衡。
使用者移動:因為使用者位置有可能改變,所以必須經常重新確定使用者位置來檢測移動。
變化精度:來自每個定位方式的位置估計值的準確性並不一致,從一個來源獲得的10秒前的位置可能反而比來自另一個或相同來源的最新位置更精確。
這些挑戰都導致了定位服務的困難,本文件提供的資訊可幫助您應對這些挑戰,以獲得可靠的位置讀數。它還提供如何在您自己的應用中為使用者提供準確、可靠地理位置的經驗。
2.如何向系統請求定位
在解決上述定位誤差之前,我們先來介紹一下如何在安卓裝置中獲得使用者的位置資訊。
安卓中位置資訊的獲取依賴於回撥策略,當你需要獲得位置資訊的時候,你需要通過requestLocationUpdates()
函式向LocationManager(即”Location Manager位置管家”)發出請求,告知它你需要位置資訊的更新,並傳遞一個監聽器LocationListener給它。你的LocationListener
必須要完成對一系列回撥函式的實現,當位置改變或者定位服務的狀態改變,這些函式將會被LocationManager
呼叫。
舉個例子來說,下面的程式碼展示瞭如何定義一個LocationListener並請求定位更新:
// 獲取對系統位置管理器的引用
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// 定義一個迴應位置更新的監聽器
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// 當一個新的位置由網路位置提供時呼叫
makeUseOfNewLocation(location);
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
// 把這個監聽器註冊到Location Manager上來接受位置更新
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
其中requestLocationUpdates()
的第一個引數是位置提供來源的型別(在這個例子中定位來源是網路定位—來自於訊號塔和WiFi)。你可以通過第二個和第三個引數自定義你的監聽器的位置更新接收頻率,其中第二個引數是兩次定位最短的時間間隔、第三個引數是兩次定位最小位置變化,這個例子中設定為0即儘可能的頻繁更新位置。而最後一個引數則是你想要註冊到LocationManager
上的LocationListener
。
如果想要通過GPS感測器來獲取位置,使用GPS_PROVIDER
來代替NETWORK_PROVIDER
。你也可以通過呼叫requestLocationUpdates()
函式兩次來同時使用GPS和網路請求位置更新—一個使用GPS_PROVIDER
,另一個使用NETWORK_PROVIDER
。
向用戶取得定位許可權
為了從GPS_PROVIDER
和NETWORK_PROVIDER
來定位,你必須在安卓應用manifest檔案中分別宣告ACCESS_COARSE_LOCATION
、ACCESS_FINE_LOCATION
這兩個向用戶請求定位的許可權。如果沒有這些許可權,你的應用將會在位置請求上失敗。
如果你正同時使用GPS_PROVIDER
和NETWORK_PROVIDER
,那麼你只需要請求一次ACCESS_FINE_LOCATION
許可權即可,因為這個許可權的範圍包括了以上兩種來源。而ACCESS_COARSE_LOCATION
許可權則只允許使用NETWORK_PROVIDER
。
警告:如果您的應用是針對ndroid 5.0(API級別21)或更高版本,則必須在應用的manifest檔案中宣告使用android.hardware.location.network或android.hardware.location.gps硬體功能,具體取決於您的應用是否接收來自NETWORK_PROVIDER或GPS_PROVIDER的位置更新。 如果您的應用從這些位置來源中獲取位置資訊,則需要在您的manifest檔案中宣告您的應用使用了這些硬體功能。 對於執行Android 5.0(API 21)之前版本的裝置,請求ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION許可權包含了位置硬體功能的隱含請求。 但是,請求這些許可權不會自動請求Android 5.0(API級別21)及更高版本上的位置硬體功能。
下面的程式碼樣例演示了一個從GPS感測器獲取資料的應用如何在manifest檔案中宣告許可權和硬體資訊:
<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>
3. 根據情況選擇一個最優的定位策略
基於定位服務的應用現在遍地都是了,但是由於總是低於理想的精度、使用者的移動、獲取位置方式的多樣性和節電需求,獲取使用者位置是極為複雜的。為了跨越節電的情況下提供較準確的定位這個挑戰,你必須選擇一系列合適的模型來決定你的應用怎麼獲取位置。這個決策模型包括了你什麼時候開始、停止監聽位置更新和什麼時候使用快取的位置資料。
獲取使用者位置的流程圖
下面是一個典型的獲取使用者位置程式的流程圖:
- 開啟應用;
- 一段時間後,從選擇的位置監聽器開始監聽位置變化;
- 通過過濾掉新的但是並不準確的位置來保持“最佳的定位”;
- 停止監聽位置更新;
- 盡其用最好的定位資料。
這樣的視窗模型,需要你在構建需要位置服務的應用程式做出許多重要決定。
決定什麼時候開始監聽位置更新
你一定想要讓監聽器從你的應用啟動開始或者僅僅在使用者開啟某項功能時就開始監聽,但注意較長的定位監聽視窗會消耗大量的電量,而短週期的定位服務可能沒有那麼精確。
正如上述,你可以通過呼叫requestLocationUpdates()
函式來開始監聽位置更新:
String locationProvider = LocationManager.NETWORK_PROVIDER;
// 或者使用GPS感測器提供的位置資訊:
// String locationProvider = LocationManager.GPS_PROVIDER;
locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
從最後一個已知的定位快速獲取位置資訊
位置監聽器接收到第一個準確定位往往需要的時間對於使用者來說太長,所以在得到一個很準確的位置之前最好先通過getLastKnownLocation(String)
提供給使用者一個快取好的位置資訊:
String locationProvider = LocationManager.NETWORK_PROVIDER;
// 或者使用LocationManager.GPS_PROVIDER
Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
決定什麼時候停止監聽位置更新
決定什麼時候不再需要新的定位的邏輯依賴於你的應用,這可能相當簡單也可能十分複雜。在獲取位置和使用位置之間的短暫空隙能夠提高定位的準確性,並且一定要時刻記住長時間的監聽位置更新必然會消耗大量電量,所以一旦你獲得了你需要的位置資訊,你就應該通過removeUpdates(PendingIntent)
來停止監聽位置更新:
// 登出掉註冊在LocationManager上的LocationListener
locationManager.removeUpdates(locationListener);
保持當前最佳的定位資料
你一定希望最近一次的定位時最準確的,但是由於定位的多樣性,大多數最近的定位都不是最佳的。你應該制定一些標準來選擇定位資料,這種標準往往也會由於應用和測試的實際情況而呈現出多樣性。
下面是一些用來確定定位精度的步驟:
1. 檢查新得到的位置是否完全比原來的位置資料更新;
2. 檢查新得到的位置在精度上是比原來的位置資料更準確還是更差;
3. 檢查新的位置資訊來源於何種位置來源,決定你更願意相信哪個。
一個精心準備的例子如下:
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定位來取代兩者都用。只使用一種服務能有效的在較為潛在的精準度風險中減少電池消耗。
常見的應用情況
有很多原因可能需要在應用程式中獲取使用者位置。 以下是一些情況,您可以使用使用者位置豐富您的應用程式。 每個場景還描述了當您應該開始和停止監聽位置時的良好做法,以便獲得良好的定位資料並幫助保持電池壽命。
為使用者建立的內容打上位置標記
您可能正在建立一個應用程式,其中使用者建立的內容標記有位置資訊。 使用者能分享他們的本地體驗、釋出對餐廳的評論或記錄一些可以用他們當前位置增強的內容。 關於位置服務的這種互動應該如何處理的模型可以參考下圖:
這與之前在程式碼中如何獲得使用者位置的模型相呼應。 為了獲得最佳的位置精確度,您可以選擇在使用者開始建立內容時或甚至在應用程式啟動時開始監聽位置更新,然後在內容準備釋出或記錄時停止監聽更新。 您可能需要考慮建立內容的典型任務需要花費多長時間,並判斷此持續時間是否允許收集新的位置資料。
幫助使用者決定去哪裡的應用
您可能正在建立一個應用程式,試圖向用戶提供一組有關去哪裡的選項。 例如,您希望提供附近的餐館,商店和娛樂的列表,並且根據使用者位置更改建議順序。
為了達成如下的流程,你可能要選擇:
- 當一個新的位置到來的時候,重新安排推薦服務的順序;
- 每當推薦序列被確定的時候停止監聽位置更新。
這樣的模型在下圖中可以體現: