Android获取经纬度的完美解决方案

 更新时间:2024年03月26日 14:12:55   作者:Time@traveler  
Android中获取定位信息的方式有很多种,系统自带的LocationManager,都能帮助我们获取当前经纬度,但第三方厂商一般都需要申请相关的key,且调用量高时,还会产生资费问题,这篇文章主要介绍了Android获取经纬度的完美解决方案,需要的朋友可以参考下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

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())
        }
      }
    }
  }

注:经纬度转换地址时,需要开启一个线程或者协程进行转换,不然会阻塞主线程,引发异常。

到此这篇关于Android获取经纬度的最佳实现方式的文章就介绍到这了,更多相关Android获取经纬度内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家!

相关文章

  • Android 实现两个Activity跳转实例

    Android 实现两个Activity跳转实例

    本文主要介绍Android 多个Activity相互之间的跳转,认识Activity生命周期,在做Android编程的时候用处很大,希望能帮助有需要的小伙伴
    2016-07-07
  • Android实现简单的拨号器功能

    Android实现简单的拨号器功能

    这篇文章主要为大家详细介绍了Android实现简单的拨号器功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • Android开发中听筒无法播放音乐的解决方法

    Android开发中听筒无法播放音乐的解决方法

    这篇文章主要介绍了Android开发中听筒无法播放音乐的解决方法,涉及Android权限控制中的相关属性设置技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2016-10-10
  • Android编程使用sax解析xml数据的方法详解

    Android编程使用sax解析xml数据的方法详解

    这篇文章主要介绍了Android编程使用sax解析xml数据的方法,结合实例形式详细分析了Android使用sax解析xml数据的操作步骤及界面布局、单元测试等相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • Android?Camera+SurfaceView自动聚焦防止变形拉伸

    Android?Camera+SurfaceView自动聚焦防止变形拉伸

    这篇文章主要为大家介绍了Android自定义相机Camera+SurfaceView实现自动聚焦防止变形拉伸详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Android基本游戏循环实例分析

    Android基本游戏循环实例分析

    这篇文章主要介绍了Android基本游戏循环,以完整实例形式较为详细的分析了Android实现基本游戏循环的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-10-10
  • Android新特性页面之ViewPager拖拽到最后一页再拖拽打开其他Activity(三种方法)

    Android新特性页面之ViewPager拖拽到最后一页再拖拽打开其他Activity(三种方法)

    这篇文章主要介绍了Android新特性页面之ViewPager拖拽到最后一页再拖拽打开其他Activity的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • Flutter实现App功能引导页

    Flutter实现App功能引导页

    这篇文章主要为大家详细介绍了Flutter实现App功能引导页,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • Android实现手机定位的案例代码

    Android实现手机定位的案例代码

    今天小编就为大家分享一篇关于Android实现手机定位的案例代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • Kotlin Fragment的具体使用详解

    Kotlin Fragment的具体使用详解

    Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿,而且我们普通手机开发也会加入这个Fragment, 我们可以把他看成一个小型的Activity,又称Activity片段
    2022-10-10

最新评论

?


http://www.vxiaotou.com