Android使用LocationManager實現(xiàn)定位功能

一缤灵、請求位置權(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)限分類:

  • 類別:
    1. 前臺位置信息
    2. 后臺位置信息
  • 精確度:
    1. 確切位置
    2. 大致位置
前臺位置信息

建議您聲明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 分鐘計算一次位置。如果其他應用請求位置猪勇,則您的應用可在最多兩分鐘后獲得這些信息设褐。

參考:http://www.reibang.com/p/0d52ca1cb85b

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泣刹,隨后出現(xiàn)的幾起案子助析,更是在濱河造成了極大的恐慌,老刑警劉巖椅您,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件外冀,死亡現(xiàn)場離奇詭異,居然都是意外死亡掀泳,警方通過查閱死者的電腦和手機雪隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來员舵,“玉大人脑沿,你說我怎么就攤上這事÷砥В” “怎么了庄拇?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長韭邓。 經(jīng)常有香客問我措近,道長,這世上最難降的妖魔是什么女淑? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任瞭郑,我火速辦了婚禮,結(jié)果婚禮上鸭你,老公的妹妹穿的比我還像新娘凰浮。我一直安慰自己,他們只是感情好苇本,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布袜茧。 她就那樣靜靜地躺著,像睡著了一般瓣窄。 火紅的嫁衣襯著肌膚如雪笛厦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天俺夕,我揣著相機與錄音裳凸,去河邊找鬼贱鄙。 笑死,一個胖子當著我的面吹牛姨谷,可吹牛的內(nèi)容都是我干的逗宁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼梦湘,長吁一口氣:“原來是場噩夢啊……” “哼瞎颗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捌议,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤哼拔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓣颅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倦逐,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年宫补,在試婚紗的時候發(fā)現(xiàn)自己被綠了檬姥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡粉怕,死狀恐怖穿铆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斋荞,我是刑警寧澤荞雏,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站平酿,受9級特大地震影響凤优,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜈彼,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一筑辨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧幸逆,春花似錦棍辕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拍顷,卻和暖如春抚太,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工尿贫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留电媳,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓庆亡,卻偏偏與公主長得像匾乓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子又谋,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345