位置策略
注意:本指南中描述的策略適用于 android.location 中的 Platform Location API树叽。Google Location Services API 是 Google Play 服務(wù)的一部分,它提供了一種更強(qiáng)大的高級(jí)框架,可自動(dòng)處理位置信息提供程序凭疮、用戶移動(dòng)和位置信息準(zhǔn)確性亡笑。它還可以根據(jù)您提供的耗電量參數(shù)處理位置信息更新安排嘲叔。在大多數(shù)情況下窿给,通過使用 Location Services API拳缠,您將獲得更好的電池性能以及更適當(dāng)?shù)臏?zhǔn)確性覆获。
要詳細(xì)了解 Location Services API马澈,請(qǐng)參閱適用于 Android 的 Google 位置信息服務(wù)。
了解用戶的位置可以讓應(yīng)用更加智能弄息,并為用戶提供更好的信息痊班。在針對(duì) Android 開發(fā)具備位置感知能力的應(yīng)用時(shí),您可以利用 GPS 和 Android 網(wǎng)絡(luò)位置信息提供程序獲取用戶的位置信息摹量。雖然 GPS 最為準(zhǔn)確涤伐,但它只能在戶外工作,并會(huì)快速消耗電池電量缨称,而且無法像用戶期望的那樣快速返回位置信息凝果。Android 網(wǎng)絡(luò)位置信息提供程序根據(jù)手機(jī)信號(hào)塔和 WLAN 信號(hào)確定用戶位置信息,在室內(nèi)外均可工作睦尽,響應(yīng)速度更快器净,并且電池耗電量更少。要在應(yīng)用中獲取用戶位置信息当凡,您既可以同時(shí)使用 GPS 和網(wǎng)絡(luò)位置信息提供程序山害,也可以只使用其中的一種纠俭。
確定用戶位置信息存在的挑戰(zhàn)
在移動(dòng)設(shè)備上獲取用戶位置信息可能是件很復(fù)雜的任務(wù)。位置信息讀數(shù)(無論來源是什么)可能因?yàn)槎喾N原因而包含錯(cuò)誤并且不準(zhǔn)確浪慌。用戶位置信息的一些錯(cuò)誤來源包括:
-
位置信息來源眾多
GPS冤荆、Cell-ID 和 WLAN 均可提供用戶位置信息線索。確定究竟使用和信任哪種方式权纤,需要在準(zhǔn)確性钓简、速度和電池能效方面做出權(quán)衡。
-
用戶移動(dòng)
由于用戶位置信息會(huì)發(fā)生變化汹想,因此您必須不時(shí)地重新估算用戶位置信息涌庭,從而將移動(dòng)情況考慮在內(nèi)。
-
準(zhǔn)確性不同
每個(gè)位置信息來源產(chǎn)生的位置信息估算數(shù)據(jù)準(zhǔn)確性不一致欧宜。10 秒前從一個(gè)來源獲取的位置信息可能比從另一個(gè)或同一個(gè)來源獲取的最新位置信息更準(zhǔn)確坐榆。
這些問題導(dǎo)致我們難以獲取可靠的用戶位置信息讀數(shù)。本文檔提供的信息可以幫助您應(yīng)對(duì)這些挑戰(zhàn)冗茸,以獲取可靠的位置信息讀數(shù)席镀。本文還提供了一些供您在應(yīng)用中采用的建議,從而為用戶提供準(zhǔn)確且靈敏的地理位置信息體驗(yàn)夏漱。
請(qǐng)求位置信息更新
在解決上述某些位置信息錯(cuò)誤之前豪诲,我們先來介紹一下如何在 Android 上獲取用戶位置信息。
您可以通過回調(diào)在 Android 中獲取用戶位置信息挂绰。您可以通過調(diào)用 requestLocationUpdates() 并傳入 LocationListener 以從 LocationManager 接收位置信息更新屎篱。在用戶位置信息或服務(wù)狀態(tài)發(fā)生變化時(shí),您的 LocationListener 必須實(shí)現(xiàn) LocationManager 調(diào)用的多個(gè)回調(diào)方法葵蒂。
注意:在搭載 Android 8.0(API 級(jí)別 26)及更高版本的設(shè)備上交播,如果應(yīng)用正在后臺(tái)運(yùn)行并請(qǐng)求當(dāng)前位置信息,則設(shè)備每小時(shí)僅計(jì)算幾次位置信息践付。要了解如何讓應(yīng)用適應(yīng)這些計(jì)算限制秦士,請(qǐng)參閱后臺(tái)位置信息限制。
以下代碼展示了如何定義 LocationListener 和請(qǐng)求位置信息更新:
// 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() 中的第一個(gè)參數(shù)是要使用的位置信息提供程序的類型(在本例中永高,是獲取基于手機(jī)信號(hào)塔和 WLAN 的位置信息的網(wǎng)絡(luò)位置信息提供程序)隧土。您可以通過第二個(gè)和第三個(gè)參數(shù)控制監(jiān)聽器接收更新的頻率(第二個(gè)參數(shù)是通知之間的時(shí)間間隔下限,第三個(gè)參數(shù)是通知之間的距離變化下限)命爬;如需盡可能頻繁地請(qǐng)求位置信息通知曹傀,只需將這兩個(gè)參數(shù)都設(shè)為 0 即可。最后一個(gè)參數(shù)是 LocationListener饲宛,用于接收位置信息更新的回調(diào)皆愉。
要從 GPS 提供程序請(qǐng)求位置信息更新,請(qǐng)使用 GPS_PROVIDER,而不是 NETWORK_PROVIDER亥啦。您還可以同時(shí)從 GPS 和網(wǎng)絡(luò)位置信息提供程序請(qǐng)求位置信息更新炭剪,只需調(diào)用 requestLocationUpdates() 兩次即可(一次針對(duì) NETWORK_PROVIDER,另一次針對(duì) GPS_PROVIDER)翔脱。
請(qǐng)求用戶權(quán)限
要從 NETWORK_PROVIDER 或 GPS_PROVIDER 接收位置信息更新奴拦,您必須通過在 Android 清單文件中分別聲明 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 權(quán)限來請(qǐng)求用戶權(quán)限。如果沒有這些權(quán)限届吁,應(yīng)用將在請(qǐng)求位置信息更新時(shí)在運(yùn)行時(shí)失敗错妖。
如果您同時(shí)使用 NETWORK_PROVIDER 和 GPS_PROVIDER,則只需請(qǐng)求 ACCESS_FINE_LOCATION 權(quán)限疚沐,因?yàn)樗槍?duì)這兩個(gè)提供程序的權(quán)限暂氯。針對(duì) ACCESS_COARSE_LOCATION 的權(quán)限只允許訪問 NETWORK_PROVIDER。
注意:如果您的應(yīng)用以 Android 5.0(API 級(jí)別 21)或更高版本為目標(biāo)平臺(tái)亮蛔,則必須在清單文件中聲明應(yīng)用使用的是 android.hardware.location.network 或 android.hardware.location.gps 硬件功能痴施,具體取決于應(yīng)用是從 NETWORK_PROVIDER 還是 GPS_PROVIDER 接收位置信息更新。如果應(yīng)用從這兩個(gè)位置信息提供來源之一接收位置信息究流,則需要在應(yīng)用清單中聲明應(yīng)用使用了相應(yīng)的硬件功能辣吃。在搭載 Android 5.0 (API 21) 之前版本的設(shè)備中,請(qǐng)求 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 權(quán)限包含對(duì)位置信息硬件功能的隱含請(qǐng)求芬探。不過神得,在 Android 5.0(API 級(jí)別 21)及更高版本上,請(qǐng)求這些權(quán)限并不會(huì)自動(dòng)請(qǐng)求位置信息硬件功能偷仿。
以下代碼示例演示了如何在應(yīng)用的清單文件中聲明用于從設(shè)備的 GPS 讀取數(shù)據(jù)的權(quán)限和硬件功能:
<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>
定義模型以實(shí)現(xiàn)最佳性能
基于位置信息的應(yīng)用現(xiàn)在已經(jīng)司空見慣了哩簿,但由于準(zhǔn)確性不理想、用戶會(huì)移動(dòng)酝静、獲取位置信息的方法眾多以及節(jié)省電池電量的需求节榜,獲取用戶位置信息就變得十分復(fù)雜。要在節(jié)省電池電量的同時(shí)克服那些妨礙您獲取優(yōu)質(zhì)用戶位置信息的障礙形入,您必須定義一個(gè)一致的模型以指定應(yīng)用獲取用戶位置信息的方式全跨。此模型包含您開始和停止監(jiān)聽更新的時(shí)間缝左,以及使用緩存的位置信息數(shù)據(jù)的時(shí)間亿遂。
獲取用戶位置信息的流程
以下是獲取用戶位置信息的一般流程:
啟動(dòng)應(yīng)用。
稍后渺杉,開始監(jiān)聽來自所需位置信息提供程序的更新蛇数。
過濾掉不太準(zhǔn)確的新修正項(xiàng),使位置信息保持“當(dāng)前的最佳估算值”是越。
停止監(jiān)聽位置信息更新耳舅。
利用最近一次最佳位置信息估算值。
圖 1 以時(shí)間軸的形式演示了一個(gè)這樣的模型,直觀呈現(xiàn)了應(yīng)用監(jiān)聽位置信息更新的時(shí)間段浦徊,以及在這段時(shí)間內(nèi)發(fā)生的事件馏予。
圖 1. 表示應(yīng)用監(jiān)聽位置信息更新的窗口的時(shí)間軸。
當(dāng)您將基于位置信息的服務(wù)添加到應(yīng)用中時(shí)盔性,會(huì)根據(jù)此窗口模型(系統(tǒng)在此期間接收位置信息更新)做出許多決策霞丧。
決定何時(shí)開始監(jiān)聽更新
您可能需要在應(yīng)用啟動(dòng)后立即開始監(jiān)聽位置信息更新,或僅在用戶激活特定功能后才開始監(jiān)聽冕香。請(qǐng)注意蛹尝,長(zhǎng)時(shí)間監(jiān)聽位置信息修正可能會(huì)消耗大量的電池電量,但短時(shí)間監(jiān)聽可能無法達(dá)到足夠的準(zhǔn)確性悉尾。
如上所示突那,您可以通過調(diào)用 requestLocationUpdates() 開始監(jiān)聽更新:
String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or, use GPS location data:
// String locationProvider = LocationManager.GPS_PROVIDER;
locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
根據(jù)最近一次的已知位置信息進(jìn)行快速修正
位置信息監(jiān)聽器往往需要很長(zhǎng)時(shí)間才能接收到第一個(gè)位置信息修正項(xiàng),導(dǎo)致用戶等得不耐煩构眯。在為位置信息監(jiān)聽器提供更準(zhǔn)確的位置之前愕难,您應(yīng)該通過調(diào)用 getLastKnownLocation(String) 利用緩存的位置信息:
String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or use LocationManager.GPS_PROVIDER
Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
保持當(dāng)前的最佳估算值
您可能會(huì)認(rèn)為最新的位置信息修正項(xiàng)是最準(zhǔn)確的信息。不過惫霸,由于位置信息修正項(xiàng)的準(zhǔn)確性不盡相同务漩,所以最新的修正項(xiàng)并不一定是最佳的。您應(yīng)該包含根據(jù)多個(gè)條件選擇位置信息修正項(xiàng)的邏輯它褪。這些條件也因應(yīng)用和現(xiàn)場(chǎng)測(cè)試的用例而有所不同饵骨。
您可以執(zhí)行以下幾個(gè)步驟以驗(yàn)證位置信息修正項(xiàng)的準(zhǔn)確性:
檢查檢索到的位置信息是否明顯比之前的估算值更新。
檢查位置信息所聲明的準(zhǔn)確性優(yōu)于還是劣于之前的估算值茫打。
檢查新的位置信息來自哪個(gè)提供程序居触,并確定您是否更信任此提供程序。
此邏輯的詳細(xì)示例大致如下:
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);
}
調(diào)整模型以節(jié)省電池電量和數(shù)據(jù)交換
在測(cè)試應(yīng)用時(shí)老赤,您可能會(huì)發(fā)現(xiàn)用于提供優(yōu)質(zhì)位置信息和良好性能的模型需要進(jìn)行一些調(diào)整轮洋。您可以采取以下措施,從而在二者之間達(dá)到很好的平衡抬旺。
減小窗口的大小
監(jiān)聽位置信息更新的窗口越小弊予,意味著與 GPS 和網(wǎng)絡(luò)位置信息服務(wù)的交互就越少,因而可以延長(zhǎng)電池續(xù)航時(shí)間开财。但這樣一來汉柒,從中選擇最佳估算值的位置信息也就越少。
將位置信息提供程序設(shè)為以較低的頻率返回更新
降低新更新在窗口期間出現(xiàn)的速度也能提高電池能效责鳍,但準(zhǔn)確性會(huì)降低碾褂。權(quán)衡值取決于應(yīng)用的使用方式。您可以增大 requestLocationUpdates() 中用于指定間隔時(shí)間和距離變化下限的參數(shù)历葛,從而降低更新速度正塌。
限制一組提供程序
根據(jù)應(yīng)用的使用環(huán)境和所需的準(zhǔn)確性級(jí)別,您可以選擇僅使用網(wǎng)絡(luò)位置信息提供程序或僅使用 GPS,而不是同時(shí)使用這兩種服務(wù)乓诽。僅與其中一種服務(wù)進(jìn)行交互可以減少電池用量帜羊,但可能會(huì)降低準(zhǔn)確性。
常見的應(yīng)用案例
許多原因都可能會(huì)促使您想要在應(yīng)用中獲取用戶位置信息鸠天。在以下幾種場(chǎng)景中逮壁,您可以使用用戶位置信息改進(jìn)您的應(yīng)用。每個(gè)場(chǎng)景還介紹了與應(yīng)該開始和停止監(jiān)聽位置信息的時(shí)間有關(guān)的最佳做法粮宛,以便獲得優(yōu)質(zhì)讀數(shù)并延長(zhǎng)電池續(xù)航時(shí)間窥淆。
使用位置信息標(biāo)記用戶創(chuàng)建的內(nèi)容
您可能正在創(chuàng)建一款應(yīng)用,其中用戶創(chuàng)建的內(nèi)容將使用位置信息進(jìn)行標(biāo)記巍杈。假設(shè)用戶會(huì)分享其當(dāng)?shù)伢w驗(yàn)忧饭、發(fā)布餐廳評(píng)論,或記錄一些能夠使用當(dāng)前位置信息進(jìn)行增強(qiáng)的內(nèi)容筷畦。圖 2 中的模型直觀呈現(xiàn)了在使用位置信息服務(wù)時(shí)词裤,這種互動(dòng)可能會(huì)如何發(fā)生。
圖 2. 表示窗口的時(shí)間軸鳖宾,在此窗口中吼砂,系統(tǒng)會(huì)獲取用戶位置信息,并在用戶使用當(dāng)前位置信息后停止監(jiān)聽鼎文。
這與之前有關(guān)如何在代碼中獲取用戶位置信息的模型(圖 1)是一致的渔肩。為了達(dá)到最高的位置信息準(zhǔn)確性,您可以選擇在用戶開始創(chuàng)建內(nèi)容時(shí)拇惋,甚至是在應(yīng)用啟動(dòng)時(shí)開始監(jiān)聽位置信息更新周偎,然后在內(nèi)容隨時(shí)可供發(fā)布或記錄時(shí)停止監(jiān)聽更新。您可能需要考慮典型的內(nèi)容創(chuàng)建任務(wù)需要多長(zhǎng)時(shí)間撑帖,并判斷是否能夠在此時(shí)長(zhǎng)內(nèi)有效地收集位置信息估算值蓉坎。
幫助用戶決定去哪里
您可能正在創(chuàng)建一款應(yīng)用,它會(huì)針對(duì)“去哪里”這一問題為用戶提供一系列選項(xiàng)胡嘿。例如蛉艾,您想要提供附近的餐廳、商店和娛樂場(chǎng)所列表衷敌,并且推薦順序會(huì)根據(jù)用戶位置信息而發(fā)生變化勿侯。
為了適應(yīng)這樣的流程,您可以選擇:
在獲取新的最佳估算值后重新排列建議
如果推薦順序已經(jīng)穩(wěn)定逢享,則停止監(jiān)聽更新
圖 3 直觀展示了這種模型罐监。
圖 3. 表示窗口的時(shí)間軸,在此窗口中瞒爬,每當(dāng)用戶位置信息更新時(shí),系統(tǒng)就會(huì)更新一組動(dòng)態(tài)數(shù)據(jù)。
提供模擬位置信息數(shù)據(jù)
在開發(fā)應(yīng)用時(shí)侧但,您必須要測(cè)試模型在獲取用戶位置信息方面的性能矢空。使用實(shí)際 Android 設(shè)備即可輕松地實(shí)現(xiàn)這一點(diǎn)。不過禀横,如果沒有設(shè)備屁药,您仍然可以測(cè)試基于位置信息的功能,只需在 Android 模擬器中模擬位置信息數(shù)據(jù)即可柏锄。您可以使用設(shè)備的開發(fā)者選項(xiàng)中提供的模擬位置信息選項(xiàng)酿箭,或在模擬器控制臺(tái)中使用 geo 命令,將模擬位置信息數(shù)據(jù)發(fā)送到您的應(yīng)用趾娃。
注意:提供的模擬位置信息數(shù)據(jù)將作為 GPS 位置信息注入缭嫡,因此您必須從 GPS_PROVIDER 請(qǐng)求位置信息更新,才能讓模擬位置信息數(shù)據(jù)正常工作抬闷。
使用開發(fā)者選項(xiàng)
在設(shè)備上啟用開發(fā)者選項(xiàng)和 USB 調(diào)試妇蛀,然后按照與使用選擇模擬位置信息應(yīng)用選項(xiàng)有關(guān)的說明操作。
在模擬器控制臺(tái)中使用 geo 命令
要從命令行發(fā)送模擬位置信息數(shù)據(jù)笤成,請(qǐng)執(zhí)行以下操作:
在 Android 模擬器中啟動(dòng)您的應(yīng)用评架,然后在 SDK 的 /tools 目錄中打開終端/控制臺(tái)。
-
連接到模擬器控制臺(tái):
telnet localhost <console-port>
發(fā)送位置信息數(shù)據(jù):
-
geo fix 用于發(fā)送固定的地理位置信息炕泳。
此命令接受十進(jìn)制的經(jīng)度和緯度纵诞,還選擇接受海拔高度(以米為單位)。例如:
geo fix -121.45356 46.51119 4392
-
geo nmea 用于發(fā)送 NMEA 0183 語(yǔ)句培遵。
此命令接受“
GPRMC”(瞬時(shí)數(shù)據(jù))類型的單個(gè) NMEA 語(yǔ)句挣磨。 例如:
geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
要了解如何連接到模擬器控制臺(tái),請(qǐng)參閱使用模擬器控制臺(tái)荤懂。