默說:現(xiàn)在在 Android 開發(fā)的時候需要獲取用戶的地理位置已經(jīng)愈發(fā)的簡單,各種地圖 SDK 都提供精準的定位方法。不過如果你的需求是只需模糊定位到用戶的城市,那樣的話,系統(tǒng) API 完全能滿足你的需求,這時候再去集成一個地圖 SDK 就感覺過重了。網(wǎng)上使用系統(tǒng) API 進行定位的文章都比較早了,而且 Android 6.0 版本加入了危險權限的動態(tài)驗證,所以基本沒有個工具類能直接拿來就用的,那我只能再組裝個輪子給大家使用了。沒啥技術含量,大牛輕拍。
動圖鎮(zhèn)樓

Android 定位管家:LocationManager
基本使用
- 獲取 LocationManager
- 判斷定位服務(GPS,WIFI,基站)是否可用
- 設置定位監(jiān)聽,獲取經(jīng)緯度
public void initLocation(Context context){
// 獲取 LocationManager
mLocationManager = (LocationManager) context
.getSystemService(Context.LOCATION_SERVICE);
// 判斷網(wǎng)絡定位是否可用,可替換成 GPS 定位。
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
0, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
//位置發(fā)生改變時回調(diào)該函數(shù)
location.getLatitude();//緯度
location.getLongitude();//經(jīng)度
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
//狀態(tài)改變回調(diào)
//provider:定位器名稱(NetWork,Gps等)
//status: 3中狀態(tài),超出服務范圍,臨時不可用,正??捎? //extras: 包含定位器一些細節(jié)信息
}
@Override
public void onProviderEnabled(String provider) {
//定位開啟回調(diào)
}
@Override
public void onProviderDisabled(String provider) {
//定位關閉回調(diào)
}
});
}
}
所需權限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
基本上,使用了這幾個函數(shù)就能獲取到經(jīng)緯度了,是不是超級簡單,這里使用網(wǎng)絡進行定位是因為其定位速度比較快,且在室內(nèi)也可以使用,雖然精度沒有 gps 定位的高,但是我們的精度只到城市級別,所以這點誤差完全可以接受。
定位工具類封裝:LocationUtils
LocationUtils類代碼:
package cn.motalks.mtdc.utils;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
/**
* Created by MoLin on 16/9/25.
*/
public class LocationUtils {
private volatile static LocationUtils uniqueInstance;
private LocationHelper mLocationHelper;
private MyLocationListener myLocationListener;
private LocationManager mLocationManager;
private Context mContext;
private LocationUtils(Context context) {
mContext = context;
mLocationManager = (LocationManager) context
.getSystemService( Context.LOCATION_SERVICE );
}
//采用Double CheckLock(DCL)實現(xiàn)單例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
uniqueInstance = new LocationUtils( context );
}
}
}
return uniqueInstance;
}
/**
* 初始化位置信息
* @param locationHelper 傳入位置回調(diào)接口
*/
public void initLocation(LocationHelper locationHelper) {
Location location = null;
mLocationHelper = locationHelper;
if (myLocationListener == null) {
myLocationListener = new MyLocationListener();
}
// 需要檢查權限,否則編譯報錯,想抽取成方法都不行,還是會報錯。只能這樣重復 code 了。
if ( Build.VERSION.SDK_INT >= 23 &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return ;
}
if (mLocationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
location = mLocationManager.getLastKnownLocation( LocationManager.NETWORK_PROVIDER );
Log.e("MoLin", "LocationManager.NETWORK_PROVIDER");
if (location != null) {
locationHelper.UpdateLastLocation(location);
}
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
0, 0, myLocationListener);
} else {
Log.e("MoLin", "LocationManager.GPS_PROVIDER");
location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location != null) {
locationHelper.UpdateLastLocation(location);
}
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
1000, 50, myLocationListener);
}
}
private class MyLocationListener implements LocationListener {
//定位服務狀態(tài)改變會觸發(fā)該函數(shù)
@Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
Log.e("MoLin", "onStatusChanged!");
if (mLocationHelper != null) {
mLocationHelper.UpdateStatus(provider, status, extras);
}
}
// 定位功能開啟時會觸發(fā)該函數(shù)
@Override
public void onProviderEnabled(String provider) {
Log.e("MoLin", "onProviderEnabled!" + provider);
}
// 定位功能關閉時會觸發(fā)該函數(shù)
@Override
public void onProviderDisabled(String provider) {
Log.e("MoLin", "onProviderDisabled!" + provider);
}
// 當坐標改變時觸發(fā)此函數(shù),如果Provider傳進相同的坐標,它就不會被觸發(fā)
@Override
public void onLocationChanged(Location location) {
Log.e("MoLin", "onLocationChanged!");
if (mLocationHelper != null) {
mLocationHelper.UpdateLocation(location);
}
}
}
// 移除定位監(jiān)聽
public void removeLocationUpdatesListener() {
// 需要檢查權限,否則編譯不過
if ( Build.VERSION.SDK_INT >= 23 &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return ;
}
if (mLocationManager != null) {
mLocationManager.removeUpdates(myLocationListener);
}
}
}
LocationUtils類的使用
這里的動態(tài)權限管理使用了都有米同學封裝的 EasyPermissionsEx 類。(這個類下面會詳細介紹)
package cn.motalks.mtdc.ui;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.GpsStatus;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import cn.motalks.mtdc.R;
import cn.motalks.mtdc.utils.EasyPermissionsEx;
import cn.motalks.mtdc.utils.LocationHelper;
import cn.motalks.mtdc.utils.LocationUtils;
/**
* Created by MoLin on 16/9/25.
*/
public class LocationTest2Activity extends Activity {
private TextView mLatitudeView;
private TextView mLongtitudeView;
private TextView mWeatherView;
private Button mButton;
private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
private static final int RC_SETTINGS_SCREEN = 2;
private String[] mNeedPermissionsList = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.location_text_activity);
initView();
super.onCreate(savedInstanceState);
}
private void initView() {
mLatitudeView = (TextView) findViewById(R.id.location_latitude);
mLongtitudeView = (TextView) findViewById(R.id.location_longtitude);
mWeatherView = (TextView) findViewById(R.id.weather_info_tv);
mButton = (Button) findViewById(R.id.location_button);
mButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
// 使用了 EasyPermissionsEx 類來管理動態(tài)權限配置
if (EasyPermissionsEx.hasPermissions(LocationTest2Activity.this, mNeedPermissionsList)) {
initLocation();
} else {
EasyPermissionsEx.requestPermissions(LocationTest2Activity.this, "需要定位權限來獲取當?shù)靥鞖庑畔?, PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION, mNeedPermissionsList);
}
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.e("MoLin", "已獲取權限!");
initLocation();
} else {
if (EasyPermissionsEx.somePermissionPermanentlyDenied(this, mNeedPermissionsList)) {
EasyPermissionsEx.goSettings2Permissions(this, "需要定位權限來獲取當?shù)靥鞖庑畔?但是該權限被禁止,你可以到設置中更改"
, "去設置", RC_SETTINGS_SCREEN);
}
}
}
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RC_SETTINGS_SCREEN) {
Toast.makeText(this, "settings", Toast.LENGTH_LONG).show();
}
}
private void initLocation() {
LocationUtils.getInstance(LocationTest2Activity.this).initLocation( new LocationHelper() {
@Override
public void UpdateLocation(Location location) {
Log.e("MoLin", "location.getLatitude():" + location.getLatitude());
mLatitudeView.setText(location.getLatitude() + "");
mLongtitudeView.setText(location.getLongitude() + "");
}
@Override
public void UpdateStatus(String provider, int status, Bundle extras) {
}
@Override
public void UpdateGPSStatus(GpsStatus pGpsStatus) {
}
@Override
public void UpdateLastLocation(Location location) {
Log.e("MoLin", "UpdateLastLocation_location.getLatitude():" + location.getLatitude());
mLatitudeView.setText(location.getLatitude() + "");
mLongtitudeView.setText(location.getLongitude() + "");
}
});
}
@Override
protected void onDestroy() {
// 在頁面銷毀時取消定位監(jiān)聽
LocationUtils.getInstance(LocationTest2Activity.this).removeLocationUpdatesListener();
super.onDestroy();
}
}
動態(tài)權限配置
在Android 6.0及其以上版本上,系統(tǒng)在APP安裝時授權所有普通權限,危險權限需要在使用時動態(tài)讓用戶授權。這使得Android的權限管理更加靈活,用戶可以根據(jù)需要在設置應用中對應用的各個危險權限授予不同的權限。Android系統(tǒng)的權限管理不知道被多少人吐槽過,這一改進無疑是加分項。
危險權限介紹
下圖是所有危險權限的列表,在使用時需要動態(tài)讓用戶授權。每個對應的危險權限都會對應一個權限組,若你申請了一個危險權限,得到用戶允許后即可獲得對應權限組的其他權限。<font color="red">例如你在申請了讀短信的權限,用戶點擊了允許,那么你再申請獲取寫短信的權限時,系統(tǒng)會立即授予而不會再彈出對話框要求用戶確認。</font>

動態(tài)權限管理工具類
Google 之前在 github 上開源了一個 EasyPermissions ,但是會在解釋為什么需要權限的時候彈出一個對話框阻塞了用戶操作,并且在用戶勾選"禁止后不再詢問"后依然會彈出對話框請求權限,用戶體驗不太好。EasyPermissionsEx 一個國人基于 EasyPermissions 封裝的類庫,使用說明見《解讀Android官方開發(fā)指導 - 運行時權限》。
Demo GitHub 地址
https://github.com/apkcoder/AndroidLocationUtils
三胖:看圖說話
