Android中获取定位信息的方式有很多种,系统自带的LocationManager,以及第三方厂商提供的一些定位sdk,都能帮助我们获取当前经纬度,但第三方厂商一般都需要申请相关的key,且调用量高时,还会产生资费问题。这里采用LocationManager + FusedLocationProviderClient 的方式进行经纬度的获取,以解决普通场景下获取经纬度和经纬度转换地址的功能。
一,添加定位权限
<!--允许获取精确位置,精准定位必选--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!--后台获取位置信息,若需后台定位则必选--> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!--用于申请调用A-GPS模块,卫星定位加速--> <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获取当前经纬度
获取经纬度时,可根据自己的诉求进行参数自定义,如果对经纬度要求不是很精确的可以自行配置Criteria里面的参数。
获取定位前需要先判断相关的服务是否可用,获取定位的服务其实有很多种选择,因为个人项目对经纬度准确性要求较高,为了保证获取的成功率和准确性,只使用了GPS和网络定位两种,如果在国内还会有基站获取等方式,可以自行修改。
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对象 * @minDistance 位置变化最小距离:当位置距离变化超过此值时,将更新位置信息(单位:米) * @timeOut 超时时间,如果超时未返回,则直接使用默认值 */ @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 { //超时未返回则直接获取失败,返回默认值 withTimeout(timeOut){ suspendCancellableCoroutine {continuation -> //获取最佳定位方式,如果获取不到则默认采用网络定位。 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}") } //超时直接返回默认的空对象 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 -> //获取最佳定位方式,如果获取不到则默认采用网络定位。 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 { //获取最佳定位方式,如果获取不到则默认采用网络定位。 var currentProvider = mLocationManager.getBestProvider(createCriteria(), true) if (currentProvider.isNullOrEmpty()||currentProvider == "passive"){ currentProvider = "network" } return mLocationManager.getLastKnownLocation(currentProvider) ?: createDefaultLocation() } //创建定位默认值 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
在获取经纬度时会出现各种异常的场景,会导致成功的回调一直无法触发,这里使用了协程,如果超过指定超时时间未返回,则直接默认为获取失败,进行下一步的处理。
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()) } } } /** * 获取当前定位,需要申请定位权限 * */ @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()) } } } } //创建当前LocationRequest对象 private fun createLocationRequest():CurrentLocationRequest{ return CurrentLocationRequest.Builder() .setDurationMillis(1000) .setMaxUpdateAgeMillis(5000) .setPriority(Priority.PRIORITY_HIGH_ACCURACY) .build() } //创建默认值 private fun createDefaultLocation():Location{ val location = Location("network") location.longitude = 0.0 location.latitude = 0.0 return location } }
五,整合LocationManager和FusedLocationProviderClient
在获取定位时,可能会出现GPS定位未开启的情况,所以不管是LocationManager或FusedLocationProviderClient都需要判断当前服务是否可用,获取定位时,如果GPS信号较弱等异常情况下,就需要考虑到获取定位超时的情况,这里使用了协程,如FusedLocationProviderClient超过3秒未获取成功,则直接切换到LocationManager进行二次获取,这是提升获取经纬度成功的关键。
在实际项目中,如果对获取经纬度有较高的考核要求时,通过结合LocationManager和FusedLocationProviderClient如果还是获取不到,可考虑集成第三方的进行进一步获取,可以考虑使用华为的免费融合定位服务,因为我们使用过百度地图的sdk,每天会出现千万分之五左右的定位错误和定位漂移问题。
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) } /** * 打开定位服务设置 */ 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 } } /** * 获取当前定位 */ @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) //默认使用FusedLocationProviderClient 如果FusedLocationProviderClient不可用或获取失败,则使用LocationManager进行二次获取 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 //检查LocationManager是否可用 return if (LocationManagerUtils.checkLocationManagerAvailable(locationManager)){ //使用LocationManager获取当前经纬度 val location = LocationManagerUtils.getCurrentPosition(locationManager, timeOut) if (location.latitude == 0.0){ LocationManagerUtils.getLastLocation(locationManager) }else{ location } }else{ //获取失败,则采用默认经纬度 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进行定位 val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) return if (FusedLocationProviderUtils.checkFusedLocationProviderAvailable(fusedLocationClient)){ withContext(Dispatchers.IO){ //使用FusedLocationProviderClient获取当前经纬度 val location = FusedLocationProviderUtils.getCurrentPosition(fusedLocationClient) if (location.latitude == 0.0){ FusedLocationProviderUtils.getLastLocation(fusedLocationClient) }else{ location } } }else{ LocationManagerUtils.createDefaultLocation() } } }
注:因为获取定位是比较耗电的操作,在实际使用时,可增加缓存机制,比如2分钟之内频繁,则返回上一次缓存的数据,如果超过2分钟则重新获取一次,并缓存起来。
六,获取当前经纬度信息或经纬度转换地址
1,获取当前经纬度
@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,经纬度转换地址
/** * @param latitude 经度 * @param longitude 纬度 * @return 详细位置信息 */ 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 "" } }
调用时:
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()) } } } }
注:经纬度转换地址时,需要开启一个线程或者协程进行转换,不然会阻塞主线程,引发异常。