專案需求討論 - 定位功能小結

青蛙要fly發表於2018-07-16

前言:

我們知道我們的APP有可能需要獲取一些地理位置資訊。比如定位使用者當前的位置,自動選定城市或者區域等。所以這次做個關於定位的一些總結。

專案需求討論 - 定位功能小結

正文

我們按照二大塊來進行分析:一塊是相關許可權,一塊是具體獲取地理資訊的相關程式碼。(而實際開發程式碼中,程式碼這二塊是寫在一起的,單純是為了文章分析從而分開。)

專案需求討論 - 定位功能小結

1.相關許可權

專案需求討論 - 定位功能小結

這裡的許可權我特指了二塊:

  1. 一個是本身我們平常開發的app需要獲取各種許可權,比如相機等,這時候我們既然要獲取當前手機的地理資訊,肯定也要有一個Location相關的許可權。
  2. 本身手機需要開啟相應的定位功能,不然app有許可權獲取,但是手機關閉了整個的定位功能,就還是獲取不到。

1.1 app獲取手機許可權

emmm......這塊我覺得應該不需要花更多的時間來說明了吧,主要就是:

  1. 檢查許可權 (checkSelfPermission)
  2. 請求許可權(requestPermissions)
  3. 回撥事件處理(onRequestPermissionsResult) 而我們要申請的許可權無非就是Location相關的許可權。
android.permission.ACCESS_COARSE_LOCATION 
允許一個程式訪問CellID或WiFi熱點來獲取粗略的位置
android.permission.ACCESS_FINE_LOCATION 
允許一個程式訪問精良位置(如GPS)
複製程式碼

我們可以看到第一個許可權中的英文單詞COARSE粗略的意思,所以在想要粗略的獲取一個地理位置的時候,比如我們通過網路來獲取,我們只需要申請這個許可權即可;第二個許可權中的英文單詞FINE說明是精確度高的,比如我們需要通過GPS來獲取許可權的時候,我們就需要申請這個許可權。

一般來說我們的app這二個許可權都會申請,因為會需要GPS配合網路一起來確定地理位置資訊。

1.2 手機的定位開關

在確定我們的app本身已經具有了定位許可權後,我們需要知道本身的手機是否已經開啟了定位功能。

public static boolean isLocServiceEnable(Context context) {
    LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    if (gps || network) {
       return true;
    }
    return false;
}
複製程式碼

我們可以看到上面我們提過一般來獲取定位是靠GPS和NetWork二種(為啥是一般呢,因為還有一種 PASSIVE,後面會講到)。所以我們需要判定這二個功能是否可用。(如果使用者把定位功能給關了,那肯定二個都返回false。)

專案需求討論 - 定位功能小結

那這時候假如我們發現使用者把定位功能關了。我們肯定需要提示使用者,然後協助使用者跳到該設定介面,從而讓使用者把定位功能開啟 (畢竟一般的普通使用者,可能還真的讓他去設定介面找,一時半會還真找不到,畢竟安卓機型太多,每個地方都不同 )。

比如我們彈出一個彈框,提示使用者,按確定按鈕的時候跳轉到設定的定位介面:

AlertDialog.Builder builder  = new AlertDialog.Builder(activity);
builder.setMessage("尚未開啟位置定位服務");
builder.setPositiveButton("開啟", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
            //啟動定位Activity
            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
           //比如我們這裡設定requestCode為 1 
            activity.startActivityForResult(intent , 1);
      }
});

builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
       }
});
複製程式碼

我們可以看到通過隱式啟動Action為Settings.ACTION_LOCATION_SOURCE_SETTINGS即可,但是這裡記得用使用startActivityForResult而不是startActivity,我看很多網上的的寫法是用startActivity,單純跳轉過去是沒問題,但是我們需要知道返回的結果,萬一使用者跳轉過去後沒有開啟呢。

既然我們用了statActivityForResult來啟動,當我們返回回到自己的app介面的時候,在onActivityForResult中需要來判斷,本來因為習慣性思維,所以以為自動在onActivityForResult的返回引數resultCode可以用來判斷,後來發現不管開啟不開啟,都是返回RESULT_CANCELED,也就是0,畢竟在那個設定介面我們並沒有設定setResult(xxx);所以當判斷了requestCode之後,我們需要重新判斷一次定位是否可用了。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
      
    if (requestCode == 1) {
       //我們通過上面提過的方法,再次去判斷是否gps和network的provider都無效。
       if (!isLocServiceEnable(MainActivity.this)) {
           Toast.makeText(this, "未開啟定位功能,請手動選擇地址位置", Toast.LENGTH_LONG).show();
       } else {
           //去獲取具體的地理位置資訊...
       }
    } 
}
複製程式碼

2 獲取地理位置

我們上面提到了我們想要獲取地理位置的時候,需要具備上面的基本許可權,然後才能正常使用我們的相關api去獲取資訊。

專案需求討論 - 定位功能小結

主要是通過```LocationManager``這個類。

專案需求討論 - 定位功能小結

但是android.location包下的並不是谷歌推薦的:

專案需求討論 - 定位功能小結

翻譯過來就是:此API不是訪問Android位置的推薦方法。 Google位置服務API是Google Play服務的一部分,是向您的應用新增位置感知功能的首選方式。 它提供了更簡單的API,更高的精度,低功耗的地理圍欄等等。 如果您當前正在使用android.location API,強烈建議您儘快切換到Google Location Services API。

而是推薦the Google Location Services API ,然後你懂得....emmm........

2.1 直接獲取地理資訊

使用getLastKnownLocation方法獲取:

//獲取LocationManager的例項物件
locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
//獲取支援的provider列表
List<String> providers = locationManager.getProviders(true);
Location bestLocation = null;
//遍歷provider列表
for (String provider : providers) {
    //通過getLastKnowLocation方法來獲取
    Location l = locationManager.getLastKnownLocation(provider);
    if (l == null) {
         continue;
    }
    if (bestLocation == null || l.getAccuracy() < bestLocation.getAccuracy()) {
         // Found best last known location: %s", l);
         bestLocation = l;
     }
}
複製程式碼

這裡需要注意的是,為啥通過迴圈provider來獲取,比如有些人會問,我開啟了GPS,我想通過GPS來定位,我不是直接getLastKnowLocation(LocationManager.GPS_PROVIDER)就可以了嗎?理論上是沒問題的,但是大部分時候獲取到的都是null , 畢竟GPS本身定位時間也會很久,而且如果在室內就更加GG了。

所以網上經常看到有提問:

專案需求討論 - 定位功能小結

當然解決方式也有很多,有些人直接通過while迴圈,比如一直請求:

while(xxx = null){
  xxx = getLastKnowLocation(LocationManager.GPS_PROVIDER);
}
複製程式碼

這還不算坑爹,我用了華為和小米手機,小米手機使用這個GPS來獲取Location,一下子就獲取了。華為我寫了while迴圈,等了很久很久,也還是一直是null。(居然還跟不同牌子手機都有關係)

所以最終我是遍歷了provider來獲取最佳的地址來解決的,如果獲取不到GPS定位,也會有network輔助。

也可以參考相關的連結瞭解一下:Android 成功 使用GPS獲取當前地理位置(解決getLastKnownLocation 返回 null),不過貌似也沒有找到百分百直接獲取GPS定位獲取資訊的方式。


2.2 監視位置變化

使用requestLocationUpdates方法來獲取。

public void requestLocationUpdates(
String provider, 
long minTime, 
float minDistance,
LocationListener listener) 
{

}
複製程式碼

我們可以看到傳入provider,最小更新時間,最小的更新距離,然後就是回撥listener。

所以我們重點在於LocationListener:

專案需求討論 - 定位功能小結

mLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
             //比如判斷location是否為null,然後根據Location來轉換成相關的地址位置資訊。
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
                  
        }

        @Override
        public void onProviderEnabled(String provider) {
            
        }

        @Override
        public void onProviderDisabled(String provider) {
                    //比如在provider失效了就取消監聽
                    locationManager.removeUpdates(this);
        }
};
複製程式碼

切記,在某個你需要的條件下,通過removeUpdates()去取消監聽,比如你可能在onPause中去取消等。

我們在onLocationChanged方法中獲取到了Location物件,就可以去獲取相關資訊了。

  1. 通過Location來獲取相關的經緯度:
    專案需求討論 - 定位功能小結
double latitude = location.getLatitude();
double longitude = location.getLongitude();
複製程式碼
  1. 通過Geocoder來把經緯度轉換成相應的Address集合:
    專案需求討論 - 定位功能小結
Geocoder geocoder = new Geocoder(context);
List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
複製程式碼
  1. 最後通過Address物件中的相關屬性,拼接出自己想要的相關資訊。
address.getCountryName() //國家
address.getPostalCode() //郵編
address.getCountryCode() //國家編碼
address.getAdminArea() //省份
address.getSubAdminArea() //二級省份
address.getThoroughfare() //道路
address.getSubLocality() //二級城市
.......
.......

複製程式碼

結語:

emm.......大家輕噴即可。。。。

相關文章