Android獲取經(jīng)緯度的完美解決方案
Android中獲取定位信息的方式有很多種,系統(tǒng)自帶的LocationManager,以及第三方廠商提供的一些定位sdk,都能幫助我們獲取當(dāng)前經(jīng)緯度,但第三方廠商一般都需要申請(qǐng)相關(guān)的key,且調(diào)用量高時(shí),還會(huì)產(chǎn)生資費(fèi)問題。這里采用LocationManager + FusedLocationProviderClient 的方式進(jìn)行經(jīng)緯度的獲取,以解決普通場(chǎng)景下獲取經(jīng)緯度和經(jīng)緯度轉(zhuǎn)換地址的功能。
一,添加定位權(quán)限
<!--允許獲取精確位置,精準(zhǔn)定位必選--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!--后臺(tái)獲取位置信息,若需后臺(tái)定位則必選--> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!--用于申請(qǐng)調(diào)用A-GPS模塊,衛(wèi)星定位加速--> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
二,添加依賴庫
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' implementation 'com.google.android.gms:play-services-location:21.0.1'
三,使用LocationManager獲取當(dāng)前經(jīng)緯度
獲取經(jīng)緯度時(shí),可根據(jù)自己的訴求進(jìn)行參數(shù)自定義,如果對(duì)經(jīng)緯度要求不是很精確的可以自行配置Criteria里面的參數(shù)。
獲取定位前需要先判斷相關(guān)的服務(wù)是否可用,獲取定位的服務(wù)其實(shí)有很多種選擇,因?yàn)閭€(gè)人項(xiàng)目對(duì)經(jīng)緯度準(zhǔn)確性要求較高,為了保證獲取的成功率和準(zhǔn)確性,只使用了GPS和網(wǎng)絡(luò)定位兩種,如果在國(guó)內(nèi)還會(huì)有基站獲取等方式,可以自行修改。
import android.Manifest.permission import android.location.* import android.os.Bundle import android.util.Log import androidx.annotation.RequiresPermission import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout import kotlin.coroutines.resume object LocationManagerUtils { val TAG = "LocationManagerUtils" /** * @mLocationManager 傳入LocationManager對(duì)象 * @minDistance 位置變化最小距離:當(dāng)位置距離變化超過此值時(shí),將更新位置信息(單位:米) * @timeOut 超時(shí)時(shí)間,如果超時(shí)未返回,則直接使用默認(rèn)值 */ @RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION]) suspend fun getCurrentPosition( mLocationManager: LocationManager, timeOut: Long = 3000, ):Location{ var locationListener : LocationListener?=null return try { //超時(shí)未返回則直接獲取失敗,返回默認(rèn)值 withTimeout(timeOut){ suspendCancellableCoroutine {continuation -> //獲取最佳定位方式,如果獲取不到則默認(rèn)采用網(wǎng)絡(luò)定位。 var bestProvider = mLocationManager.getBestProvider(createCriteria(),true) if (bestProvider.isNullOrEmpty()||bestProvider == "passive"){ bestProvider = "network" } Log.d(TAG, "getCurrentPosition:bestProvider:${bestProvider}") locationListener = object : LocationListener { override fun onLocationChanged(location: Location) { Log.d(TAG, "getCurrentPosition:onCompete:${location.latitude},${location.longitude}") if (continuation.isActive){ continuation.resume(location) mLocationManager.removeUpdates(this) } } override fun onProviderDisabled(provider: String) { } override fun onProviderEnabled(provider: String) { } } //開始定位 mLocationManager.requestLocationUpdates(bestProvider, 1000,0f, locationListener!!) } } }catch (e:Exception){ try { locationListener?.let { mLocationManager.removeUpdates(it) } }catch (e:Exception){ Log.d(TAG, "getCurrentPosition:removeUpdate:${e.message}") } //超時(shí)直接返回默認(rèn)的空對(duì)象 Log.d(TAG, "getCurrentPosition:onError:${e.message}") return createDefaultLocation() } } @RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION]) suspend fun repeatLocation(mLocationManager: LocationManager):Location{ return suspendCancellableCoroutine {continuation -> //獲取最佳定位方式,如果獲取不到則默認(rèn)采用網(wǎng)絡(luò)定位。 var bestProvider = mLocationManager.getBestProvider(createCriteria(),true) if (bestProvider.isNullOrEmpty()||bestProvider == "passive"){ bestProvider = "network" } Log.d(TAG, "getCurrentPosition:bestProvider:${bestProvider}") val locationListener = object : LocationListener { override fun onLocationChanged(location: Location) { Log.d(TAG, "getCurrentPosition:onCompete:${location.latitude},${location.longitude}") if (continuation.isActive){ continuation.resume(location) } mLocationManager.removeUpdates(this) } override fun onProviderDisabled(provider: String) { } override fun onProviderEnabled(provider: String) { } } //開始定位 mLocationManager.requestLocationUpdates(bestProvider,1000, 0f, locationListener) } } @RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION]) fun getLastLocation( mLocationManager: LocationManager): Location { //獲取最佳定位方式,如果獲取不到則默認(rèn)采用網(wǎng)絡(luò)定位。 var currentProvider = mLocationManager.getBestProvider(createCriteria(), true) if (currentProvider.isNullOrEmpty()||currentProvider == "passive"){ currentProvider = "network" } return mLocationManager.getLastKnownLocation(currentProvider) ?: createDefaultLocation() } //創(chuàng)建定位默認(rèn)值 fun createDefaultLocation():Location{ val location = Location("network") location.longitude = 0.0 location.latitude = 0.0 return location } private fun createCriteria():Criteria{ return Criteria().apply { accuracy = Criteria.ACCURACY_FINE isAltitudeRequired = false isBearingRequired = false isCostAllowed = true powerRequirement = Criteria.POWER_HIGH isSpeedRequired = false } } ///定位是否可用 fun checkLocationManagerAvailable(mLocationManager: LocationManager):Boolean{ return mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)|| mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) } }
四,使用FusedLocationProviderClient
在獲取經(jīng)緯度時(shí)會(huì)出現(xiàn)各種異常的場(chǎng)景,會(huì)導(dǎo)致成功的回調(diào)一直無法觸發(fā),這里使用了協(xié)程,如果超過指定超時(shí)時(shí)間未返回,則直接默認(rèn)為獲取失敗,進(jìn)行下一步的處理。
import android.Manifest import android.app.Activity import android.content.Context import android.content.Context.LOCATION_SERVICE import android.content.Intent import android.location.Geocoder import android.location.Location import android.location.LocationManager import android.provider.Settings import android.util.Log import androidx.annotation.RequiresPermission import com.google.android.gms.location.LocationServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import java.io.IOException import java.util.* import kotlin.coroutines.resume object FusedLocationProviderUtils { val TAG = "FusedLocationUtils" @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) suspend fun checkFusedLocationProviderAvailable(fusedLocationClient: FusedLocationProviderClient):Boolean{ return try { withTimeout(1000){ suspendCancellableCoroutine { continuation -> fusedLocationClient.locationAvailability.addOnFailureListener { Log.d(TAG, "locationAvailability:addOnFailureListener:${it.message}") if (continuation.isActive){ continuation.resume(false) } }.addOnSuccessListener { Log.d(TAG, "locationAvailability:addOnSuccessListener:${it.isLocationAvailable}") if (continuation.isActive){ continuation.resume(it.isLocationAvailable) } } } } }catch (e:Exception){ return false } } ///獲取最后已知的定位信息 @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) suspend fun getLastLocation(fusedLocationClient: FusedLocationProviderClient):Location{ return suspendCancellableCoroutine {continuation -> fusedLocationClient.lastLocation.addOnSuccessListener { if (continuation.isActive){ Log.d(TAG, "current location success:$it") if (it != null){ continuation.resume(it) }else{ continuation.resume(createDefaultLocation()) } } }.addOnFailureListener { continuation.resume(createDefaultLocation()) } } } /** * 獲取當(dāng)前定位,需要申請(qǐng)定位權(quán)限 * */ @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) suspend fun getCurrentPosition(fusedLocationClient: FusedLocationProviderClient): Location { return suspendCancellableCoroutine {continuation -> fusedLocationClient.getCurrentLocation(createLocationRequest(),object : CancellationToken(){ override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken { return CancellationTokenSource().token } override fun isCancellationRequested(): Boolean { return false } }).addOnSuccessListener { if (continuation.isActive){ Log.d(TAG, "current location success:$it") if (it != null){ continuation.resume(it) }else{ continuation.resume(createDefaultLocation()) } } }.addOnFailureListener { Log.d(TAG, "current location fail:$it") if (continuation.isActive){ continuation.resume(createDefaultLocation()) } }.addOnCanceledListener { Log.d(TAG, "current location cancel:") if (continuation.isActive){ continuation.resume(createDefaultLocation()) } } } } //創(chuàng)建當(dāng)前LocationRequest對(duì)象 private fun createLocationRequest():CurrentLocationRequest{ return CurrentLocationRequest.Builder() .setDurationMillis(1000) .setMaxUpdateAgeMillis(5000) .setPriority(Priority.PRIORITY_HIGH_ACCURACY) .build() } //創(chuàng)建默認(rèn)值 private fun createDefaultLocation():Location{ val location = Location("network") location.longitude = 0.0 location.latitude = 0.0 return location } }
五,整合LocationManager和FusedLocationProviderClient
在獲取定位時(shí),可能會(huì)出現(xiàn)GPS定位未開啟的情況,所以不管是LocationManager或FusedLocationProviderClient都需要判斷當(dāng)前服務(wù)是否可用,獲取定位時(shí),如果GPS信號(hào)較弱等異常情況下,就需要考慮到獲取定位超時(shí)的情況,這里使用了協(xié)程,如FusedLocationProviderClient超過3秒未獲取成功,則直接切換到LocationManager進(jìn)行二次獲取,這是提升獲取經(jīng)緯度成功的關(guān)鍵。
在實(shí)際項(xiàng)目中,如果對(duì)獲取經(jīng)緯度有較高的考核要求時(shí),通過結(jié)合LocationManager和FusedLocationProviderClient如果還是獲取不到,可考慮集成第三方的進(jìn)行進(jìn)一步獲取,可以考慮使用華為的免費(fèi)融合定位服務(wù),因?yàn)槲覀兪褂眠^百度地圖的sdk,每天會(huì)出現(xiàn)千萬分之五左右的定位錯(cuò)誤和定位漂移問題。
import android.Manifest import android.app.Activity import android.content.Context import android.content.Context.LOCATION_SERVICE import android.content.Intent import android.location.Geocoder import android.location.Location import android.location.LocationManager import android.provider.Settings import android.util.Log import androidx.annotation.RequiresPermission import com.google.android.gms.location.LocationServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import java.io.IOException import java.util.* import kotlin.coroutines.resume object LocationHelper { fun getLocationServiceStatus(context: Context):Boolean{ return (context.getSystemService(LOCATION_SERVICE) as LocationManager) .isProviderEnabled(LocationManager.GPS_PROVIDER) } /** * 打開定位服務(wù)設(shè)置 */ fun openLocationSetting(context: Context):Boolean{ return try { val settingsIntent = Intent() settingsIntent.action = Settings.ACTION_LOCATION_SOURCE_SETTINGS settingsIntent.addCategory(Intent.CATEGORY_DEFAULT) settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) context.startActivity(settingsIntent) true } catch (ex: java.lang.Exception) { false } } /** * 獲取當(dāng)前定位 */ @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) suspend fun getLocation(context: Activity,timeOut: Long = 2000):Location{ val location = getLocationByFusedLocationProviderClient(context) //默認(rèn)使用FusedLocationProviderClient 如果FusedLocationProviderClient不可用或獲取失敗,則使用LocationManager進(jìn)行二次獲取 Log.d("LocationHelper", "getLocation:$location") return if (location.latitude == 0.0){ getLocationByLocationManager(context, timeOut) }else{ location } } @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) private suspend fun getLocationByLocationManager(context: Activity,timeOut: Long = 2000):Location{ Log.d("LocationHelper", "getLocationByLocationManager") val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager //檢查L(zhǎng)ocationManager是否可用 return if (LocationManagerUtils.checkLocationManagerAvailable(locationManager)){ //使用LocationManager獲取當(dāng)前經(jīng)緯度 val location = LocationManagerUtils.getCurrentPosition(locationManager, timeOut) if (location.latitude == 0.0){ LocationManagerUtils.getLastLocation(locationManager) }else{ location } }else{ //獲取失敗,則采用默認(rèn)經(jīng)緯度 LocationManagerUtils.createDefaultLocation() } } @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) private suspend fun getLocationByFusedLocationProviderClient(context: Activity):Location{ Log.d("LocationHelper", "getLocationByFusedLocationProviderClient") //使用FusedLocationProviderClient進(jìn)行定位 val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) return if (FusedLocationProviderUtils.checkFusedLocationProviderAvailable(fusedLocationClient)){ withContext(Dispatchers.IO){ //使用FusedLocationProviderClient獲取當(dāng)前經(jīng)緯度 val location = FusedLocationProviderUtils.getCurrentPosition(fusedLocationClient) if (location.latitude == 0.0){ FusedLocationProviderUtils.getLastLocation(fusedLocationClient) }else{ location } } }else{ LocationManagerUtils.createDefaultLocation() } } }
注:因?yàn)楂@取定位是比較耗電的操作,在實(shí)際使用時(shí),可增加緩存機(jī)制,比如2分鐘之內(nèi)頻繁,則返回上一次緩存的數(shù)據(jù),如果超過2分鐘則重新獲取一次,并緩存起來。
六,獲取當(dāng)前經(jīng)緯度信息或經(jīng)緯度轉(zhuǎn)換地址
1,獲取當(dāng)前經(jīng)緯度
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun getCurrentLocation(activity:Activity){ if (activity != null){ val exceptionHandler = CoroutineExceptionHandler { _, exception -> } viewModelScope.launch(exceptionHandler) { val location = LocationHelper.getLocation(activity!!) val map = HashMap<String,String>() map["latitude"] ="${location.latitude}" map["longitude"] = "${location.longitude}" } } }
2,經(jīng)緯度轉(zhuǎn)換地址
/** * @param latitude 經(jīng)度 * @param longitude 緯度 * @return 詳細(xì)位置信息 */ suspend fun convertAddress(context: Context, latitude: Double, longitude: Double): String { return try { withTimeout(3000){ suspendCancellableCoroutine { continuation -> try { val mGeocoder = Geocoder(context, Locale.getDefault()) val mStringBuilder = StringBuilder() if (Geocoder.isPresent()){ val mAddresses = mGeocoder.getFromLocation(latitude, longitude, 1) if (mAddresses!= null &&mAddresses.size >0) { val address = mAddresses[0] Log.d("LocationUtils", "convertAddress()--->$address") mStringBuilder.append(address.getAddressLine(0)?:"") .append(",") .append(address.adminArea?:address.subAdminArea?:"") .append(",") .append(address.locality?:address.subLocality?:"") .append(",") .append(address.thoroughfare?:address.subThoroughfare?:"") } } if (continuation.isActive){ continuation.resume(mStringBuilder.toString()) } } catch (e: IOException) { Log.d("LocationUtils", "convertAddress()--IOException->${e.message}") if (continuation.isActive){ continuation.resume("") } } } } }catch (e:Exception){ Log.d("LocationUtils", "convertAddress()--->timeout") return "" } }
調(diào)用時(shí):
fun covertAddress(latitude:double,longitude:double){ if (activity != null){ val exceptionHandler = CoroutineExceptionHandler { _, exception -> } viewModelScope.launch(exceptionHandler) { val hashMap = argument as HashMap<*, *> withContext(Dispatchers.IO){ val address = LocationHelper.convertAddress(activity!!, "${hashMap["latitude"]}".toDouble(), "${hashMap["longitude"]}".toDouble()) } } } }
注:經(jīng)緯度轉(zhuǎn)換地址時(shí),需要開啟一個(gè)線程或者協(xié)程進(jìn)行轉(zhuǎn)換,不然會(huì)阻塞主線程,引發(fā)異常。
到此這篇關(guān)于Android獲取經(jīng)緯度的最佳實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Android獲取經(jīng)緯度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android通過原生方式獲取經(jīng)緯度與城市信息的方法
- Android GPS獲取當(dāng)前經(jīng)緯度坐標(biāo)
- Android通過原生APi獲取所在位置的經(jīng)緯度
- Android編程實(shí)現(xiàn)根據(jù)經(jīng)緯度查詢地址并對(duì)獲取的json數(shù)據(jù)進(jìn)行解析的方法
- Android獲取當(dāng)前位置的經(jīng)緯度數(shù)據(jù)
- android通過gps獲取定位的位置數(shù)據(jù)和gps經(jīng)緯度
- Android獲取經(jīng)緯度計(jì)算距離介紹
- Android 通過當(dāng)前經(jīng)緯度獲得城市的實(shí)例代碼
- android手機(jī)獲取gps和基站的經(jīng)緯度地址實(shí)現(xiàn)代碼
相關(guān)文章
Android中AlertDialog的六種創(chuàng)建方式
這篇文章主要介紹了Android中AlertDialog的六種創(chuàng)建方式的相關(guān)資料,需要的朋友可以參考下2016-07-07Android 通過代碼設(shè)置、打開wifi熱點(diǎn)及熱點(diǎn)連接的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 通過代碼設(shè)置、打開wifi熱點(diǎn)及熱點(diǎn)連接的實(shí)現(xiàn)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-05-05Android底部導(dǎo)航欄的三種風(fēng)格實(shí)現(xiàn)
這篇文章主要介紹了Android底部導(dǎo)航欄的三種風(fēng)格實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Android實(shí)現(xiàn)垂直進(jìn)度條VerticalSeekBar
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)垂直進(jìn)度條VerticalSeekBar的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android apk 插件啟動(dòng)內(nèi)存釋放問題
這篇文章主要介紹了Android apk 插件啟動(dòng)內(nèi)存釋放問題的相關(guān)資料,需要的朋友可以參考下2017-06-06Kotlin中常見內(nèi)聯(lián)擴(kuò)展函數(shù)的使用方法教程
在Kotlin中,使用inline修飾符標(biāo)記內(nèi)聯(lián)函數(shù),既會(huì)影響到函數(shù)本身, 也影響到傳遞給它的Lambda表達(dá)式,這兩者都會(huì)被內(nèi)聯(lián)到調(diào)用處。下面這篇文章主要給大家介紹了關(guān)于Kotlin中常見內(nèi)聯(lián)擴(kuò)展函數(shù)的使用方法,需要的朋友可以參考下。2017-12-12Android使用系統(tǒng)相機(jī)進(jìn)行拍照的步驟
這篇文章主要介紹了Android使用系統(tǒng)相機(jī)進(jìn)行拍照的步驟,幫助大家更好的進(jìn)行Android開發(fā),感興趣的朋友可以了解下2020-12-12詳細(xì)分析android的MessageQueue.IdleHandler
這篇文章主要介紹了android的MessageQueue.IdleHandler用法,很有參考價(jià)值,歡迎大家在下方留言區(qū)討論。2017-11-11Android Studio 3.0 gradle提示版本太老
這篇文章主要介紹了Android Studio 3.0 gradle提示版本太老的配置和解決方法。2017-11-11