Android 定位和地圖

概述:

使用設備上的傳感器可以爲APP增加豐富的定位和運動能力, 從GPS或者網絡定位到加速度計, 陀螺儀, 溫度, 氣壓計等.

定位和地圖:

注意, 這裏介紹的是Android framework中android.location包中的定位API. Google Location Services API, 是Google Play提供的服務, 提供了更加強大高級的框架可以自動計算任務比如定位提供選擇和電源管理. 定位服務也提供了新的功能, 比如framework API中不支持的activity識別. 使用framework API的開發者以及那些只是向APP中添加位置感知的開發者, 應該認真考慮使用Location Services API. 更多關於Location ServiceAPI的信息可以參考GoogleLocation Services for Android.

定位和基於地圖的APP爲移動設備提供了豐富的體驗. 我們可以在APP內使用android.location包和Google Maps Android API創建這些功能, 下面的小節將會簡單介紹如何使用這些功能.

定位服務(LocationServices):

Android通過android.location爲APP訪問定位服務. 定位框架的核心組件是LocationManager系統服務, 它提供API來確定底層設備的位置和方向(如果可用的話).

跟其它的系統服務一樣, 我們不能直接實例化一個LocationManager. 而是通過getSystemService(Context.LOCATION_SERVICE)方法來從系統請求一個實例.該方法返回一個LocationManager實例的句柄. 一旦我們的APP擁有了一個LocationManager, 就可以做這三件事:

l  查詢所有的最後已知的用戶位置的LocationProvider的列表.

l  註冊/註銷從locationprovider定期更新用戶當前位置(被標準或者名字指定).

l  爲一個給定的intent註冊/註銷一個給定經緯度的鄰近範圍.

更多信息將在下面介紹.

GoogleMaps Android API:

通過GoogleMaps Android API, 我們可以爲APP添加基於Google Maps數據的地圖. 該API會自動處理對Google Maps服務器的訪問, 數據下載, 地圖顯示和地圖上的點擊手勢. 我們可以使用API來增加標記, 多邊形和覆蓋圖, 並更改特定地圖區域用戶的視角.

Google Maps Android API中的關鍵類是MapView.一個MapView可以通過從Google Maps服務器獲得的數據顯示一個地圖. 當MapView有一個焦點的時候, 它將會捕捉按鍵和點擊手勢來自動平移和擴展地圖, 包括處理額外的地圖片段的網絡需求. 它還提供了所有用戶控制地圖用到的UI元素. 我們的APP還可以使用MapView類方法來控制地圖編程並繪製一些重疊區.

Google Maps Android API不包括在Android平臺中, 但是可以在任何GooglePlay Store中的APP上運行(Android 2.2及以上版本). 想要集成Google Maps到APP中, 我們需要安裝Google Play服務庫到Android SDK. 更多信息可以參考Google Playservice.

定位策略:

瞭解用戶的位置可以讓我們的APP更加聰明和人性化. 當開發一個可定位的APP的時候, 我們可以利用GPS和Android的Network Location Provider來獲得用戶的位置. 儘管GPS是最準確的, 但是它只能工作在戶外, 而且非常費電, 也沒有用戶期待的那麼快的反應速度. Android的Network Location Provider使用手機信號塔和WiFi信號定位, 可以在室內和戶外獲取到位置信息, 響應迅速, 而且也消耗更少的電量. 如果在APP中想要獲取用戶的位置, 我們可以合作使用GPS和Network Location Provider, 也可以只使用其中一個.

定位要面臨的挑戰:

從一個移動設備獲取用戶位置可以很複雜. 有幾個原因導致讀取位置可能包含錯誤和誤差. 這些問題有:

1.      位置源衆多: GPS, 通信塔ID, WiFi都可以提供用戶位置的線索. 確定要用哪個源需要權衡多種因素, 比如精確度, 速度和電池效率.

2.      用戶移動: 因爲用戶的座標會變, 我們必須每隔一段時間就刷新用戶的位置.

3.      變化的精確度: 每個源提供的精確度都可能不一致. 一個10秒鐘之前從一個源取得的位置可能比最新的從其它源獲取的位置(或者同一個源)更準確.

這些問題導致想要獲取一個靠譜的用戶位置變得很複雜. 該文檔將會提供一些信息來幫助我們面對這些挑戰. 它也提供了我們可以用來使APP變得更加流暢和精確的點子供我們參考.

請求位置更新:

在Android上獲取用戶位置信息的原理是回調方式. 想要獲取用戶位置需要使用LocationManager的requestLocationUpdates(), 並傳給它一個LocationListener作爲參數. 我們的LocationListener必須實現一些回調方法給LocationManager在用戶位置發生變化或者服務狀態發生變化的時候調用. 比如下面這段代碼演示瞭如何定義一個LocationListener並請求更新位置信息:

// Acquire a reference to the system Location Manager
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

// Define alistener 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 thelistener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

requestLocationUpdates()的第一個參數是要使用的locationprovider的類型(在這裏使用的是Network Location Provider, 表示手機信號塔和WiFi). 我們可以用第二個和第三個參數來控制監聽器接收更新的頻率 – 第二個參數是兩次提示的最小時間間隔, 第三個參數是兩次提示的最小改變距離– 如果兩個都被設置爲0的話, 則表示用可用的最快頻率. 最後一個參數是LocationListener, 用於接收回調方法.

想要通過GPS Provider請求位置更新, 需要使用GPS_PROVIDER替代NETWORK_PROVIDER. 我們還可以通過調用requestLocationUpdates()兩次來同時請求GPS和NetworkLocation Provider, 一次用NETWORK_PROVIDER, 一次用GPS_PROVIDER.

請求用戶權限:

爲了從NETWORK_PROVIDER或者GPS_PROVIDER接收位置更新, 我們必須在manifest中分別聲明ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION權限來獲取用戶權限, 栗子:

<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>

如果沒有這些權限的話, APP會在請求位置更新的時候失敗. 如果我們一起使用了NETWORK_PROVIDER和GPS_PROVIDER, 那麼只需要請求ACCESS_FINE_LOCATION權限, 因爲它包含這兩種provider的權限(ACCESS_COARSE_LOCATION則只包括NETWORK_PROVIDER的權限).

定義一個最佳性能模型:

基於位置的APP現在十分的常見, 但是由於要獲取最佳的精確度, 用戶位置變化, 還有多種獲取位置的方法, 以及考慮電池消耗, 這些讓獲取用戶位置變得複雜起來. 想要獲得最佳的用戶位置並保持電池的電量, 我們必須定義一個統一的模型來指定APP如何獲取用戶位置. 這個模型包括什麼時候啓動和停止監聽位置變化和什麼時候使用緩存位置數據.

獲取用戶位置的流程:

這是典型的獲取用戶位置的流程:

1.      啓動APP.

2.      過會兒之後啓動監聽器來從Location provider獲取更新.

3.      通過過濾掉較新但是不精確的數據保留一個”當前最佳的預計位置(current best estimate)”.

4.      停止監聽位置更新.

5.      使用最後最佳的預計位置.

下圖在一個時間軸上用可視化的時段展示了這種模型, 它包含了這段時間監聽到的位置更新事件:


這個圖片展示了很多我們在使用定位APP的時候需要做的決定.

決定什麼時候啓動位置更新監聽:

我們可能想要在APP剛啓動的時候就啓動位置更新的監聽器, 或者僅在用戶激活功能之後才啓動. 長時間的監聽會消耗大量的電量, 但是短時間的監聽又不能保證精確度. 我們可以通過requestLocationUpdates()方法啓動監聽:

String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or, use GPSlocation data:
// StringlocationProvider = LocationManager.GPS_PROVIDER;

locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);

使用最後一次已知位置獲取快速修正:

通常獲取第一次位置修正都會消耗很多時間. 在取得更加精確的位置信息之前, 可以使用getLastKnownLocation(String)方法獲得一個緩存的位置信息:

String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or useLocationManager.GPS_PROVIDER

Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);

決定何時停止監聽:

決定停止監聽的邏輯根據APP的需求可能很簡單也可能很複雜. 同時還要記得定位會消耗很多電量, 應該在取得需要的信息之後就停止監聽, 停止監聽的方法是removeUpdates(PendingIntent), 栗子:

// Remove the listener you previously added
locationManager.removeUpdates(locationListener);

維護當前最佳的估計數:

我們可能會覺得最近獲取的位置信息是最準確的. 但是因爲定位精確度的變化, 最近的信息不見得總是最好的. 我們應該根據多個標準來選擇最佳位置. 這些標準應該經過用例和現場測試. 這裏是幾個可以驗證精確度的步驟:

1.      檢查檢索到的位置是否比之前的估計有明顯的更新.

2.      檢查精度是比以前的更好還是更差.

3.      檢查新的位置從哪個provider獲取並決定它是否更加值得信任.

實現這一邏輯的栗子大概長這樣:

private static final int TWO_MINUTES = 1000 * 60 * 2;

/** Determineswhether 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 whichyou 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 whetherthe 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 beenmore than two minutes since the current location, use the new location
    // because theuser has likely moved
    if (isSignificantlyNewer) {
        return true;
    // If the newlocation is more than two minutes older, it must be worse
    } else if (isSignificantlyOlder) {
        return false;
    }

    // Check whetherthe 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 theold and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());

    // Determinelocation 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;
}

/** Checkswhether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
      return provider2 == null;
    }
    return provider1.equals(provider2);
}

調整模型來節省電量和數據交換:

當我們測試APP的時候, 可能會發現我們的模型如果想要既提供好的定位又提供高性能, 可能需要一些調整. 這裏是一些我們權衡兩者可能要修改的東西:

l  減小窗口的大小: 一個更小的窗口意味着與GPS和網絡定位服務更少的交互,所以會節省電池壽命. 但是它也會導致有更少的位置信息可以選擇.

l  設置locationproviders來用更低的頻率更新: 減少刷新位置信息的頻率也可以節省電池的壽命, 但是會犧牲一定的精確度. 如何權衡取決於我們的APP邏輯. 我們可以通過減少requestLocationUpdates()的參數值來降低刷新的頻率.

l  約束一組providers:根據APP的應用場景或者需要的精確度等級, 我們可以選擇只使用Network Location Provider和GPS中的一個, 而不是全用. 這樣可以降低電池消耗, 但是同樣會降低精度.

常見應用案例:

有很多我們想要獲取用戶的位置信息的理由. 下面是兩個我們可以用它們來豐富自己的APP的使用場景. 每個場景也描述了何時啓動和停止監聽器, 以及如何節省電池壽命.

通過位置標記用戶創建的內容:

我們可能會需要創建一個APP, 允許用戶通過位置信息標記一些內容. 比如分享本地體驗, 共享一家餐廳的美食, 或者記錄一些包括當前位置的信息. 一個描述這些交互如何發生的模型, 大概長這樣:


爲了獲取最佳的精度, 我們可能選擇在用戶開始創建它們的內容甚至當應用啓動的時候就開始獲取用戶的位置信息, 而在內容準備發送或者記錄的時候停止更新位置信息. 我們可能需要考慮創建內容的任務時間大概多長, 然後決定什麼時候開始啓動位置信息更新.

幫助用戶決定去哪兒:

我們可能會創建一個嘗試提供一系列選項建議用戶去哪的APP. 比如幫助用戶找附近的餐館, 商店, 娛樂設施並根據用戶位置提供推薦的順序. 要完成這些我們可能會用到:

l  當取得一個新的最佳位置信息的時候, 重新排列推薦目標.

l  如果推薦目標穩定了, 就停止監聽位置更新.

該模型如下圖所示:


提供模擬位置數據:

如果我們正在開發自己的APP, 那麼我們可能會需要測試自己的定位模型工作的如何. 使用真實的Android設備是最簡單的方法. 但是如果我們沒有一個設備的話, 還是有方法可以測試基於位置功能的APP的, 只需要使用Android模擬器提供模擬位置數據就可以了. 有三種不同的方法發送APP模擬數據: 使用Android Studio, DDMS或者在模擬器終端使用”geo”命令. 詳情可以參考這裏.

 

總結:

定位是我們在移動設備上常用的功能, Android開發者可以使用android.location或者Google Location Services for Android來開發相關的APP.Android.location中的核心組件是LocationManager, 基本用法就是通過LocationManager查詢可用的Location Providers, 包括手機基站, GPS, WiFi, 然後開啓或者關閉位置更新監聽器來獲取位置數據. 在開發的時候, 通常會根據APP邏輯在精度和耗電量之間做權衡.


參考: https://developer.android.com/guide/topics/location/index.html

https://developer.android.com/guide/topics/location/strategies.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章