一缤灵、請求位置權(quán)限
為了保護用戶隱私租副,使用位置信息服務的應用必須請求位置權(quán)限资柔。
<manifest ... >
<!-- 請求大致位置 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 請求確切位置 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--后臺位置信息 Android 10 (API level 29) 或更高版本 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
權(quán)限分類:
- 類別:
- 前臺位置信息
- 后臺位置信息
- 精確度:
- 確切位置
- 大致位置
前臺位置信息
建議您聲明location的前臺服務類型溉苛。在 Android 10(API 級別 29)及更高版本中渺绒,您必須聲明此前臺服務類型仅炊。
<!-- Recommended for Android 9 (API level 28) and lower. -->
<!-- Required for Android 10 (API level 29) and higher. -->
<service
android:name="MyNavigationService"
android:foregroundServiceType="location" ... >
<!-- Any inner elements would go here. -->
</service>
當應用請求ACCESS_COARSE_LOCATION權(quán)限或ACCESS_FINE_LOCATION權(quán)限時斗幼,就是在聲明需要獲取前臺位置信息。
后臺位置信息
在 Android 10(API 級別 29)及更高版本中抚垄,您必須在應用的清單中聲明ACCESS_BACKGROUND_LOCATION權(quán)限蜕窿,以便請求在運行時于后臺訪問位置信息。在較低版本的 Android 系統(tǒng)中呆馁,當應用獲得前臺位置信息訪問權(quán)限時桐经,也會自動獲得后臺位置信息訪問權(quán)限。
大致位置
提供設備位置的估算值浙滤。如果此位置估算值來自LocationManagerService或FusedLocationProvider阴挣,則該估算值會精確到 3 平方公里(約 1.2 平方英里)以內(nèi)。
聲明了ACCESS_COARSE_LOCATION權(quán)限纺腊,但未聲明ACCESS_FINE_LOCATION權(quán)限畔咧,您的應用就可以收到該精確度級別的位置信息。
確切位置
提供盡可能準確的設備位置估算值揖膜。如果位置估算值來自 LocationManagerService 或 FusedLocationProvider誓沸,則此估算值通常可以精確到 50 米(160 英尺)以內(nèi)壹粟,有時甚至可以精確到幾米(10 英尺)以內(nèi)拜隧。
聲明了 ACCESS_FINE_LOCATION 權(quán)限,您的應用就可以收到該精確度級別的位置信息趁仙。
二洪添、定位使用
1、申請權(quán)限
<!--允許程序打開網(wǎng)絡套接字-->
<uses-permission android:name="android.permission.INTERNET" />
<!--允許程序獲取網(wǎng)絡狀態(tài)-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--允許程序訪問WiFi網(wǎng)絡信息-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允許程序訪問CellID或WiFi熱點來獲取粗略的位置-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--后臺位置信息 Android 10 (API level 29) 或更高版本 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
2幸撕、定義工具類
public class LocationUtils {
private volatile static LocationUtils uniqueInstance;
private LocationManager locationManager;
private Context mContext;
private static ArrayList<AddressCallback> addressCallbacks;
private AddressCallback addressCallback;
public AddressCallback getAddressCallback() {
return addressCallback;
}
public void setAddressCallback(AddressCallback addressCallback) {
this.addressCallback = addressCallback;
if(isInit){
showLocation();
}else {
isInit = true;
}
}
private static Location location;
private boolean isInit = false;//是否加載過
private LocationUtils(Context context) {
mContext = context;
getLocation();
}
//采用Double CheckLock(DCL)實現(xiàn)單例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
addressCallbacks = new ArrayList<>();
uniqueInstance = new LocationUtils(context);
}
}
}
return uniqueInstance;
}
/**
* 添加回調(diào)事件
* @param addressCallback
*/
private void addAddressCallback(AddressCallback addressCallback){
addressCallbacks.add(addressCallback);
if(isInit){
showLocation();
}
}
/**
* 移除回調(diào)事件
* @param addressCallback
*/
public void removeAddressCallback(AddressCallback addressCallback){
if(addressCallbacks.contains(addressCallback)){
addressCallbacks.remove(addressCallback);
}
}
/**
* 清空回調(diào)事件
*/
public void cleareAddressCallback(){
removeLocationUpdatesListener();
addressCallbacks.clear();
}
private void getLocation() {
//1.獲取位置管理器
locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
//添加用戶權(quán)限申請判斷
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
//2.獲取位置提供器薇组,GPS或是NetWork
// 獲取所有可用的位置提供器
List<String> providerList = locationManager.getProviders(true);
String locationProvider;
if (providerList.contains(LocationManager.GPS_PROVIDER)) {
//GPS 定位的精準度比較高外臂,但是非常耗電坐儿。
System.out.println("=====GPS_PROVIDER=====");
locationProvider = LocationManager.GPS_PROVIDER;
} else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {//Google服務被墻不可用
//網(wǎng)絡定位的精準度稍差,但耗電量比較少。
System.out.println("=====NETWORK_PROVIDER=====");
locationProvider = LocationManager.NETWORK_PROVIDER;
} else {
System.out.println("=====NO_PROVIDER=====");
// 當沒有可用的位置提供器時貌矿,彈出Toast提示用戶
Intent intent = new Intent();
intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mContext.startActivity(intent);
return;
}
//3.獲取上次的位置炭菌,一般第一次運行,此值為null
location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
// 顯示當前設備的位置信息
System.out.println("==顯示當前設備的位置信息==");
showLocation();
} else {//當GPS信號弱沒獲取到位置的時候可從網(wǎng)絡獲取
System.out.println("==Google服務被墻的解決辦法==");
getLngAndLatWithNetwork();//Google服務被墻的解決辦法
}
// 監(jiān)視地理位置變化逛漫,第二個和第三個參數(shù)分別為更新的最短時間minTime和最短距離minDistace
//LocationManager 每隔 5 秒鐘會檢測一下位置的變化情況黑低,當移動距離超過 10 米的時候,
// 就會調(diào)用 LocationListener 的 onLocationChanged() 方法酌毡,并把新的位置信息作為參數(shù)傳入克握。
locationManager.requestLocationUpdates(locationProvider, 5000, 10, locationListener);
}
//獲取經(jīng)緯度
private void showLocation() {
if(location == null){
getLocation();
}else {
double latitude = location.getLatitude();//緯度
double longitude = location.getLongitude();//經(jīng)度
// for(AddressCallback addressCallback:addressCallbacks){
// addressCallback.onGetLocation(latitude,longitude);
// }
if(addressCallback != null){
addressCallback.onGetLocation(latitude,longitude);
}
getAddress(latitude, longitude);
}
}
private void getAddress(double latitude, double longitude) {
//Geocoder通過經(jīng)緯度獲取具體信息
Geocoder gc = new Geocoder(mContext, Locale.getDefault());
try {
List<Address> locationList = gc.getFromLocation(latitude, longitude, 1);
if (locationList != null) {
Address address = locationList.get(0);
String countryName = address.getCountryName();//國家
String countryCode = address.getCountryCode();
String adminArea = address.getAdminArea();//省
String locality = address.getLocality();//市
String subLocality = address.getSubLocality();//區(qū)
String featureName = address.getFeatureName();//街道
for (int i = 0; address.getAddressLine(i) != null; i++) {
String addressLine = address.getAddressLine(i);
//街道名稱:廣東省深圳市羅湖區(qū)蔡屋圍一街深圳瑞吉酒店
System.out.println("addressLine=====" + addressLine);
}
if(addressCallback != null){
addressCallback.onGetAddress(address);
}
// for(AddressCallback addressCallback:addressCallbacks){
// addressCallback.onGetAddress(address);
// }
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void removeLocationUpdatesListener() {
if (locationManager != null) {
uniqueInstance = null;
locationManager.removeUpdates(locationListener);
}
}
private LocationListener locationListener = new LocationListener() {
// Provider的狀態(tài)在可用、暫時不可用和無服務三個狀態(tài)直接切換時觸發(fā)此函數(shù)
@Override
public void onStatusChanged(String provider, int status, Bundle arg2) {
}
// Provider被enable時觸發(fā)此函數(shù)枷踏,比如GPS被打開
@Override
public void onProviderEnabled(String provider) {
}
// Provider被disable時觸發(fā)此函數(shù)菩暗,比如GPS被關(guān)閉
@Override
public void onProviderDisabled(String provider) {
}
//當坐標改變時觸發(fā)此函數(shù),如果Provider傳進相同的坐標旭蠕,它就不會被觸發(fā)
@Override
public void onLocationChanged(Location loc) {
System.out.println("==onLocationChanged==");
// location = loc;
// showLocation();
}
};
//從網(wǎng)絡獲取經(jīng)緯度
private void getLngAndLatWithNetwork() {
//添加用戶權(quán)限申請判斷
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 10, locationListener);
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
showLocation();
}
public interface AddressCallback{
void onGetAddress(Address address);
void onGetLocation(double lat,double lng);
}
}
3停团、開始定位
//注意6.0及以上版本需要在申請完權(quán)限后調(diào)用方法
LocationUtils.getInstance(this).setAddressCallback(new LocationUtils.AddressCallback() {
@Override
public void onGetAddress(Address address) {
String countryName = address.getCountryName();//國家
String adminArea = address.getAdminArea();//省
String locality = address.getLocality();//市
String subLocality = address.getSubLocality();//區(qū)
String featureName = address.getFeatureName();//街道
LogUtils.eTag("定位地址",countryName,adminArea,locality,subLocality,featureName);
}
@Override
public void onGetLocation(double lat, double lng) {
LogUtils.eTag("定位經(jīng)緯度",lat,lng);
}
});
注意:
某些版本的手機在使用Geocoder進行地址轉(zhuǎn)換的時候會出現(xiàn)如下異常:
java.io.IOException: grpc failed
原因為:the service is not available 服務不可用 即設備不支持Geocoder
這時可以使用Google的API接口進行解析,API接口如下:
https://maps.googleapis.com/maps/api/geocode/json?latlng=37.422,-122.084&language=zh-CN&sensor=false&key=YOUR_API_KEY
這里的key需要去GoogleMap開發(fā)平臺去申請
三掏熬、定位功能節(jié)省電量
1佑稠、理解電池電量消耗
位置收集和電池電量消耗在以下幾個方面直接相關(guān):
精確度:位置數(shù)據(jù)的精度。一般而言旗芬,精確度越高舌胶,電池電量消耗越大。
頻率:位置的計算頻率岗屏。位置的計算頻率越高辆琅,電池電量消耗越大。
延遲時間:位置數(shù)據(jù)的交付速度这刷。延遲越低婉烟,電池電量消耗越大。
1.精確度
您可使用setPriority()方法指定位置的精確度暇屋,傳遞以下任意值作為參數(shù):
- PRIORITY_HIGH_ACCURACY
提供最高精確度似袁,計算過程使用盡可能多的輸入(啟用 GPS、WLAN 和蜂窩網(wǎng)絡咐刨,并使用各種各樣的傳感器會產(chǎn)生巨大的電池電量消耗昙衅。 - PRIORITY_BALANCED_POWER_ACCURACY
提供準確的位置,同時進行了耗電優(yōu)化定鸟。極少使用 GPS而涉。通常使用 WLAN 和蜂窩網(wǎng)絡信息的組合來計算設備位置。 - PRIORITY_LOW_POWER
主要依賴移動電話基站联予,避免 GPS 和 WLAN 輸入啼县,從而以最小的電池電量消耗提供粗略的(城市級)精確度材原。 - PRIORITY_NO_POWER
被動地從已計算出位置的其他應用接收位置。
大多數(shù)應用的定位需求可以通過 Balanced Power 或 Low Power 選項來滿足季眷。應僅針對在前臺運行且要求實時位置更新的應用(例如地圖應用)使用 High Accuracy 選項余蟹。
2.頻率
您可以使用以下兩種方法指定位置更新頻率:
- 使用setinterval()方法指定應用計算位置的時間間隔。
- 使用setFastestInterval()指定其他應用已計算出的位置發(fā)送至您的應用的時間間隔子刮。
您應使用 setInterval()傳遞最大值威酒。這對于后臺位置收集而言尤其如此,因為后臺位置收集通常會導致不受歡迎的電池電量消耗挺峡。應對前臺用例使用幾秒鐘的時間間隔葵孤。Android 8.0 引入的后臺位置更新限制將會執(zhí)行這些策略,但您的應用也應努力在 Android 7.0 或更低版本的設備上執(zhí)行它們橱赠。
3.延遲時間
您可使用setMaxWaitTime()方法指定延遲時間佛呻,傳遞的值通常比 setInterval()方法中指定的時間間隔大幾倍。此設置將延遲位置傳遞病线,而且多個位置更新可能分批傳遞吓著。這兩個變化有助于減少電池電量消耗。
如果您的應用無需即時位置更新送挑,則您應將向 setMaxWaitTime()方法傳遞最大值绑莺,通過犧牲延遲時間來獲得更多數(shù)據(jù)和更高的電池效率。
當使用地理圍欄時惕耕,應用應向setNotificationResponsiveness()方法傳遞一個較大值來節(jié)省耗電量纺裁。建議設置五分鐘或更大的值。
2司澎、定位最佳做法:
1.移除位置更新
造成不必要的電池電量消耗的一個常見原因是欺缘,當不再需要位置更新時,沒有移除它們挤安。
例如谚殊,當某個 Activity 的onStart()或onResume()生命周期方法中包含requestlocationUpdates()調(diào)用,但在onPause()或 onStop()生命周期方法中卻沒有相應的removeLocationUpdates()調(diào)用時蛤铜,就會發(fā)生這種情況嫩絮。
2.設置超時
為了防止電池電量消耗,應設置一個停止位置更新的合理超時围肥。通過設置超時剿干,可確保更新不會無限期地繼續(xù),并且在請求更新后未移除更新的情況下(例如由于代碼錯誤)穆刻,對應用起到保護作用置尔。
3.批處理請求
對于所有非前臺用例,將多個請求一起進行批處理氢伟。您可使用setInterval()方法指定計算位置的時間間隔榜轿。然后使用setMaxWaitTime()方法設置位置傳遞給應用的時間間隔篮愉。傳遞給 setMaxWaitTime()方法的值應是傳遞給 setInterval()方法的值的倍數(shù)。
例如差导,請考慮以下位置請求:
val request = LocationRequest()
request.setInterval(10 * 60 * 1000)
request.setMaxWaitTime(60 * 60 * 1000)
4.使用被動位置更新
val request = LocationRequest()
request.setInterval(15 * 60 * 1000)
request.setFastestInterval(2 * 60 * 1000)
您的應用大約每 15 分鐘計算一次位置。如果其他應用請求位置猪勇,則您的應用可在最多兩分鐘后獲得這些信息设褐。