Android地理位置服務解析

iyqk6887 8年前發布 | 47K 次閱讀 安卓開發 Android開發 移動開發

GPS

基于衛星發射的信號,可以推算出手機到每顆衛星的距離,根據衛星的位置,推測出手機的位置。

這是一張簡單的GPS定位原理圖,需要一點數學知識,先不討論這個細節,需要的同學看 這里

現在衛星信號全球都覆蓋了,手機一般都有GPS芯片,因此可以實現定位。GPS方式準確度是最高的,走衛星通道,不需要聯網就可以要使用。但是它的缺點也非常明顯:

  • 1.比較耗電;
  • 2.絕大部分用戶默認不開啟GPS模塊,也不會長時間開著;
  • 3.從GPS模塊啟動到獲取第一次定位數據,可能需要 比較長的時間 ;
  • 4. 只能在戶外使用 ,當有遮擋物干擾時,幾乎無法使用,如城市大樓密集的地方。

WiFi

通過獲取當前所連接的WiFi熱點的一些信息,然后訪問定位服務以獲得經緯度坐標。

這是一張簡單的WiFi定位原理圖。

因為WiFi熱點一般都是固定位置,所以只要能知道手機連接的WiFi熱點的位置,也就可以推算出手機的位置。而且由于手機一般連接的WiFi不會太遠,所以其實精確度也不會太差。也不會像GPS那樣需要耗時比較久才能獲得位置信息。

Cell-ID

采集到手機所連接的基站ID號(cellid)和其它的一些信息(MNC,MCC,LAC等),然后通過網絡訪問定位服務,獲取并返回對應的經緯度坐標。

這是一張簡單的基站定位原理圖。

現在各大運營商的基站已經覆蓋了全國大部分地區,每個基站的ID號是全球唯一的,只要有手機信號,就能接收到周圍基站的信號。基站定位的精確度不如GPS,但優點是能夠在室內用,只要網絡通暢就行。

其實各種定位方式,大體都是基于三角定位的原理,不過計算的時候會有一些自己的特點,這里先不深究背景知識了。下面進入正題。

Android系統上如何獲取地理位置

方法1:Google Play Service提供的API

這個不多說,因為國內不可用!!!

需要的同學可以自己爬梯子看下用法,比較簡單: https://developer.android.com/google/play-services/location.html

方法2:系統提供的原生API:主要就是系統的 android.location 中提供的兩個類。

  • LocationManager: 和大多數系統提供的 SystemService 一樣是單例,通過 locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 來獲取。
  • LocationListener: 非常典型的觀察者模式,需要監聽地理位置的時候,創建一個 Listener ,實現LocationListener中的幾個回調方法。把Listener傳給LocationManager,當地理位置變化的時候就會回調 onLocationChanged(Location location) 發出通知。
  • 官方指導: https://developer.android.com/guide/topics/location/strategies.html

方法3:使用百度、高德之類的地圖SDK。

這簡直就是大招了,各家都有自己的數據庫,比起系統提供的API強太多了。這個這次也不說,各家的接入文檔寫的很清楚。

使用原生API采集地理位置的方法

下面介紹一下我對使用原生API的理解,畢竟不是所有場景都需要用到大招級別的sdk,有的情況我們需要自己實現定位服務。

1.首先需要了解PROVIDER

看過前面介紹的3種定位方式之后,可以很容易理解PROVIDER是什么。其實它就對應著地理位置采集的幾種方式:

  • LocationManager.GPS_PROVIDER:通過gps來獲取地理位置的經緯度信息,優點:獲取地理位置信息精確度高,缺點:只能在戶外使用, 耗時,耗電
  • LocationManager.NETWORK_PROVIDER:通過移動網絡的基站或者WiFi來獲得地理位置,優點:只要有網絡,獲取速度快,耗電低,在室內室外都可以使用。
  • LocationManager.PASSIVE_PROVIDER:被動的接收更新的地理位置信息,而不用自己主動請求地理位置。意思就是共享手機上其他App采集的位置信息,而不是自己主動去采集。

下圖是3種Provider的特點和區別:

2.打開手機的設置

先看下原生系統中地理位置設置的界面截圖:

以原生系統為例,需要采集地理位置時,需要:

  • 打開通知欄的GPS開關
  • 進入 設置->位置信息->模式 ,打開開關。然后我們可以看到,這里有3類模式:
    • 高精確度:使用GPS、WLAN、藍牙或者移動網絡確定位置
    • 節電:使用WLAN、藍牙或者移動網絡確定位置
    • 僅限設備:使用GPS確定位置
    </li> </ul>

    PS:我發現小米手機上,即使你把通知欄里面地理位置開關關閉了,進入系統的設置界面,還是可以看到地理位置是開啟的,默認選擇的是 節電 模式。而原生系統你只要在通知欄關閉了開關,就無法使用定位服務了。這里感覺國內廠商在細節上可能會有一些不同的實現。

    3.給你的App注冊權限

    當你在代碼里面使用3種不同的Provider時,應該關注到兩個權限:

    • LocationManager.GPS_PROVIDER:android.permission.ACCESS_FINE_LOCATION
    • LocationManager.NETWORK_PROVIDER:android.permission.ACCESS_COARSE_LOCATION 或者 android.permission.ACCESS_FINE_LOCATION。
      • 當聲明ACCESS_FINE_LOCATION時,拿到的位置信息將更精確(幾十米到幾百米)
      • 當聲明ACCESS_COARSE_LOCATION時,拿到的位置會粗略一點(幾百米到幾千米)
      </li>
    • LocationManager.PASSIVE_PROVIDER:android.permission.ACCESS_COARSE_LOCATION
    • </ul>

      注意:如果聲明了ACCESS_FINE_LOCATION時,就不用再聲明ACCESS_COARSE_LOCATION了,因為ACCESS_FINE_LOCATION已經包含了使用NETWORK_PROVIDER的能力。此外從Android6.0開始,ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION已經是 dangerous permission ,開發者需要注意這一點,當用戶在運行你的App時,如果沒有授權,仍然是無法獲取到地理位置信息的。

      4.根據需求的場景寫代碼(記住要盡量省電)

      一定要省電:這是一個非常重要的用戶體驗,我們應該對自己做的App負責。什么時候開始使用地理位置服務,什么時候停止使用,我們一定要想清楚,盡量不要一直占用著這種高耗電的資源。

      4.1基本代碼

      下面看代碼,一段基本的獲取地理位置的代碼是這么寫的,這段代碼可以讓你通過異步的方式獲取到用戶的地理位置。

      // 獲得Location Manager的實例
      LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

      // 定義一個監聽器,實現onLocationChanged方法,在這個方法里面可以拿到更新后的地理位置 LocationListener locationListener = new LocationListener() { public void onLocationChanged(Location location) { // 新的Location值在這里返回,Location實例中包含著緯度、經度、海拔、精確度、更新時間等一系列信息。 makeUseOfNewLocation(location); }

      public void onStatusChanged(String provider, int status, Bundle extras) {}
      
      public void onProviderEnabled(String provider) {}
      
      public void onProviderDisabled(String provider) {}
      

      };

      // 注冊監聽器,當地理位置變化時,發出通知給Listener。這個方法很關鍵。4個參數需要了解清楚: // 第1個參數:你所使用的provider名稱,是個String // 第2個參數minTime:地理位置更新時發出通知的最小時間間隔 // 第3個參數minDistance:地理位置更新發出通知的最小距離,第2和第3個參數的作用關系是“或”的關系,也就是滿足任意一個條件都會發出通知。這里第2、3個參數都是0,意味著任何時間,只要位置有變化就會發出通知。 // 第4個參數:你的監聽器 locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);</code></pre>

      4.2如何優化

      但是實戰中一定要盡量去優化,雖然獲取地理位置只能是異步的,但是仍然不建議一直不停地監聽地理位置的變化。

      谷歌官方也給出了一個采集地理位置的思路,非常值得我們來參考。思路的基本步驟如下:

      • 啟動應用。
      • 當用戶進入到應用中需要使用地理位置場景時,選擇一個合適的Provider,開始監聽地理位置的變化。
      • 獲取系統中緩存的上次的地理位置 LastKnownLocation ,保存到當前地理位置變量 currentLocation 中作為備選值,當拿到新的地理位置后,對比兩者,選擇最優的那個繼續保存它。
      • 停止監聽地理位置的變化。
      • 使用當前維護著的這個Location作為用戶的位置。

      谷歌還給出了這個方案的一個timeline圖示。

      4.3關鍵問題

      我們比較關注下面4點:

      • 1.如何選擇一個最好的provider?
      • 2.什么時候開始監聽地理位置變化,什么時候結束?
      • 3.如何比較兩個地理位置,決定哪個更好?
      • 4.LastknownPostion怎么獲取,怎么使用?

      下面介紹我的想法:

      第1點:如何選擇一個最好的provider?

      這需要看你的需求。系統中也提供了一些方法來幫我們選擇,可以設定一個條件 Criteria ,指定帥選最符合條件的地理位置提供者,根據Cirteria指定的條件,設備會自動選擇哪種location provider。

      代碼如下:

      Criteria criteria = new Criteria();//
      criteria.setAccuracy(Criteria.ACCURACY_FINE);//設置定位精準度
      criteria.setAltitudeRequired(false);//是否要求海拔
      criteria.setBearingRequired(true);//是否要求方向
      criteria.setCostAllowed(true);//是否要求收費
      criteria.setSpeedRequired(true);//是否要求速度
      criteria.setPowerRequirement(Criteria.POWER_LOW);//設置相對省電
      criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);//設置方向精確度
      criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);//設置速度精確度
      criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);//設置水平方向精確度
      criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);//設置垂直方向精確度

      // 返回滿足條件的,當前設備可用的location provider // 當第2個參數為false時,返回當前設備所有provider中最符合條件的那個(但是不一定可用)。 // 當第2個參數為true時,返回當前設備所有可用的provider中最符合條件的那個。 String rovider = mLocationManager.getBestProvider(criteria,true);</code></pre>

      總之,一共就3個provider,其實對于大部分開發者,選來選去就是 gps or network 。

      第2點,什么時候開始,什么時候結束?

      我認為最好開啟了監聽器后,要盡可能早地結束它。也就是不要調用了 requestLocationUpdates(provider, minTime, minDistance, listener) 讓位置服務開始工作后,很長時間都不去 removeUpdates(listener) 來停止服務。

      雖然在 requestLocationUpdates 方法中,有 minTimeminDistance 參數可以設置。比如設置了60000ms的minTime,希望采更新完一次地理位置后休息60s。或者設置2000米的minDistance,希望位置變化不超過2公里,也休息。這樣做 看起來好像 是可以省電。

      但是實測中發現,如果調用 requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 2000, listener) 注冊監聽器后,系統的狀態欄上面的GPS那個小圖標一直在顯示。只要你不 removeUpdates(listener) ,他就一直在工作。其實我理解,即使你設置了minTime和minDistance,位置服務還是一直處于工作狀態的,不然它怎么知道位置變化超過了你設定的minDistance呢?

      所以我的建議是,當你調用 requestLocationUpdates 后,還應該是設置一個定時器,比如30s。當30s時間到了之后,就 removeUpdate ,不再監聽地理位置了,轉而使用備選的LastKnownLocation。當下次需要使用地理位置時,再重新注冊監聽器,監聽30s,然后就移除監聽器。如果對實時性要求高,我們可以在用戶進入App中某個需要定位服務的場景之前,采用這個方法獲取一次地理位置,把它保存下來。

      第3點,如何比較兩個Location,選出更好的那個?

      谷歌也給出了代碼示例,先看一下。

      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); }</code></pre>

      這段代碼的策略是:

      • 1.先看更新時間:設定一個時間范圍,2分鐘。
        • 如果新的Location比舊的Location獲取時間更新,且超過2分鐘,那么認為新的Location更好。
        • 如果新的Location比舊的Location獲取時間更老,且超過2分鐘,那么認為新的Location不夠好。
        • 如果新的Location比舊的Location獲取時間更新,但沒有超過2分鐘,那么看下它們的精確度。
        </li>
      • 2.再看精確度:設定一個精確度范圍,200米。
        • 如果新的Location比舊的Location精確度更高,那么認為新的Location更好。
        • 如果新的Location和舊的Location精確度相等,且獲取時間更新,那么認為新的Location更好。
        • 如果新的Location比舊的Location精確度低200m以內,且獲取時間更新,來自同一個provider,那么為認為新的Location更好。
        • 其他情況都認為舊的Location更好。
        • </ul> </li> </ul>

          這段代碼是一個參考,我們實際開發中可以更具需要去定義自己的 Better Location 策略。

          另外,從API>=17開始,Location類還增加了一個 getElapsedRealtimeNanos 方法(獲取從系統啟動后走過的時間),這是為了解決 getTime 方法(獲取UTC時間)不夠精確,容易產生誤差的問題。這個方法在比較兩個Location時將更加可靠。

          第4點,怎么獲取LastknownPostion,怎么使用?

          相信有了第3點,應該知道怎么選擇 Better Location 。至于獲取LastKnownLocation直接看代碼。

          Location gpsLocation = null;
          Location networkLocation = null;

          if (context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); }

          if (context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); }

          // 下面可以比較一下哪個更好... Location currentLocation = gpsLocation; if (isBetterLocation(currentLocation, networkLocation)){ currentLocation = networkLocation; }</code></pre>

          總結一下

          說了一大堆,我覺得平時開發的時候應該這么做:

          • 1.確定自己的應用什么時候要開始監聽地理位置變化,什么時候停止。
          • 2.選擇一個合適的provider,開始監聽它提供的地理位置變化。
          • 3.讀取系統中GPS和NETWORK這兩個Provide緩存的 LastKnownPostion ,選出Better Location保存到currentBestLocation變量中。
          • 4.監聽到地理位置更新后,把更新到的Location和保存的currentBestLocation比較,得出Better One,再保存到currentBestLocation變量中。
          • 5.使用currentBestLocation作為用戶的位置,并在合適時機移除監聽器。

           

          來自:http://unclechen.github.io/2016/09/02/Android地理位置服務解析/

           

 本文由用戶 iyqk6887 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!