前言

因为项目中经常会遇到要上传一系列设备信息的功能,为了方便使用,所以就拆分成以下系列文章来单独介绍如何获取各类设备信息

1. 基础学习

在介绍之前,先讲一些获取经纬度信息相关的基础知识

1.1 LocationManager

位置管理器

想要获取经纬度信息,主要是通过LocationManager这个类,该类不能直接new,需要通过如下方式获取

val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

获取位置信息是通过该类的getLastKnownLocation(provider:String)方法,可以看到,使用此方法需要我们传递个provider,位置提供器。

1.2 LocationProvider

位置提供器

位置提供器一共有三种

  • NETWORK_PROVIDER:通过蜂窝塔(基站)和WIFI接入点来获取
  • GPS_PROVIDER:通过GPS的方式获取
  • PASSIVE_PROVIDER:被动的通过其他程序获取经纬度
locationManager.getProviders(true)

该方法会返回一个手机所支持的位置提供器集合

1.3 精度

假如我们通过以上三种provider都能获取到Location,那么选哪种作为经纬度最合适呢?

Location有getAccuracy()方法来获取精度,看官方getAccuracy()的解释:

/**
     * Get the estimated horizontal accuracy of this location, radial, in meters.
     *
     * <p>We define horizontal accuracy as the radius of 68% confidence. In other
     * words, if you draw a circle centered at this location's
     * latitude and longitude, and with a radius equal to the accuracy,
     * then there is a 68% probability that the true location is inside
     * the circle.
     *
     * <p>This accuracy estimation is only concerned with horizontal
     * accuracy, and does not indicate the accuracy of bearing,
     * velocity or altitude if those are included in this Location.
     *
     * <p>If this location does not have a horizontal accuracy, then 0.0 is returned.
     * All locations generated by the {@link LocationManager} include horizontal accuracy.
     */

简单的理解就是:

若以该经纬度作为圆心,精度作为半径画圆,那么真实的位置落在该圆内的概率为68%。因为落在该圆内的概率是固定的68%,那么肯定是圆的半径即精度越小,就越准确。所以我们选取getAccuracy()返回值最小的那个provider。

1.4 LocationListener

有时候可能会因为没有网或者在室内,通过以上方式获取到的location均为null,那么此时我们可以开启位置信息的连续监听,当时间超过设定的秒数或者位置移动超过设定的米数时,更新位置信息。

private var locationListener: LocationListener = object : LocationListener {
    override fun onLocationChanged(location: Location) {
        Log.i(TAG, "onLocationChanged: 经纬度发生变化")
        //调用更新位置
        updateToNewLocation(location)
    }

    override fun onProviderDisabled(provider: String) {
        updateToNewLocation(null)
        Log.i(TAG, "onProviderDisabled: ")
    }

    override fun onProviderEnabled(provider: String) {
        Log.i(TAG, "onProviderEnabled: ")
    }
}
locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                60000.toLong(),			//每隔1分钟重新获取一次
                8.toFloat(),			//移动距离超过8米重新获取一次
                locationListener
            )

2. 所需权限

权限

说明

INTERNET

允许使用网络

ACCESS_FINE_LOCATION

允许使用GPS定位

ACCESS_COARSE_LOCATION

允许使用WIFI热点或基站来获取粗略的定位

3. 获取步骤

3.1 申请权限

只有申请到了ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION后,才可以进行下一步获取经纬度信息的操作,这里不多讲,完整代码如下:

class LocationActivity : AppCompatActivity() {
    
    companion object {       
        const val LOCATION_REQUEST_CODE = 1
        const val LISTENER_REQUEST_CODE = 2
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        //申请权限
        if (PermissionUtil.requestPermission(LOCATION_REQUEST_CODE, permissionList.toList(), this)) {
            //获取经纬度信息
            getLocationInfo()
        }
    }
    
     /**
     * 权限申请回调
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            //LOCATION_REQUEST_CODE权限申请
            LOCATION_REQUEST_CODE -> {
                if (PermissionUtil.verifyResult(grantResults, this)) {
                    getLocationInfo()
                } else {
                    Toast.makeText(this, "没有权限", Toast.LENGTH_SHORT).show()
                }
            }         
        }
    }
}
object PermissionUtil {
    /**
     * 验证是否有权限,没有则申请
     */
    fun requestPermission(requestCode: Int, permissionList: List<String>, context: Context): Boolean {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //没有同意需要申请的权限
            val requestPermissionList = mutableListOf<String>()
            for (permission in permissionList) {
                if (ContextCompat.checkSelfPermission(
                    context,
                    permission
                ) != PackageManager.PERMISSION_GRANTED
                   ) {
                    requestPermissionList.add(permission)
                }
            }
            if (requestPermissionList.size > 0) {
                ActivityCompat.requestPermissions(
                    context as Activity,
                    requestPermissionList.toTypedArray(),
                    requestCode
                )
                return false
            } else {
                return true
            }
        } else {
            return true
        }
    }

    /**
     *验证权限申请的结果
     */
    fun verifyResult(grantResults: IntArray,context: Context): Boolean {
        if (grantResults.isNotEmpty()) {
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(context, "必须同意所有权限才能使用该功能", Toast.LENGTH_SHORT).show()
                    return false
                }
            }
            return true
        } else {
            Toast.makeText(context, "发生未知错误", Toast.LENGTH_SHORT).show()
            return false
        }
    }
}

3.2 获取经纬度信息

在申请到权限后,进行如下操作来获取经纬度信息

  • 判断是否开启位置服务,没有则跳转至系统设置打开定位服务
    判断定位服务有没有开启主要通过以下方法
var gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)

当返回true,代表GPS定位服务开启,NETWORK_PROVIDER同理

  • 获取所有支持的provider,然后通过provider来获取定位信息
  • 当返回true,代表GPS定位服务开启,NETWORK_PROVIDER同理
  • 若所支持的provider获取到的经纬度均为空,则开启连续定位监听

完整代码如下:

fun getLocationInfo() {

    //判断是否开启位置服务,没有则跳转至设置来开启
    if (isLocationServiceOpen()) {
        //获取所有支持的provider
        val providers = locationManager.getProviders(true)
        //用来存储最优的结果
        var betterLocation: Location? = null
        for (provider in providers) {
            val location = locationManager.getLastKnownLocation(provider)
            location?.let {
                Log.i(TAG, "$provider 精度为:${it.accuracy}")               
                if (betterLocation == null) {
                    betterLocation = it
                } else {
                    //因为半径等于精度,所以精度越低代表越准确
                    if (it.accuracy < betterLocation!!.accuracy)
                    betterLocation = it
                }
            }
            if (location == null) {
                Log.i(TAG, "$provider 获取到的位置为null")
            }
        }
        betterLocation?.let {
            Log.i(TAG, "精度最高的获取方式:${it.provider} 经度:${it.longitude}  纬度:${it.latitude}")
        }
        //(四)若所支持的provider获取到的位置均为空,则开启连续定位服务        
        if (betterLocation == null) {
            for (provider in locationManager.getProviders(true)) {
                locationMonitor(provider)
            }
            Log.i(TAG, "getLocationInfo: 获取到的经纬度均为空,已开启连续定位监听")           
        }
    } else {
        Toast.makeText(this, "请跳转到系统设置中打开定位服务", Toast.LENGTH_SHORT).show()
    }
}

/**
* 判断定位服务是否开启
*/
private fun isLocationServiceOpen(): Boolean {
    var gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
    var network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
    //有一个开启就可
    return gps || network
}

3.2 开启连续监听

在1.4中我们已经讲了LocationListener,如何开启连续监听,这里就不再详细讲述

具体代码如下

fun locationMonitor(provider: String) {
    if (PermissionUtil.requestPermission(
        LISTENER_REQUEST_CODE,
        permissionList.toList(),
        this
    )
       ) {       
        locationManager.requestLocationUpdates(
            provider,
            60000.toLong(),		//超过1分钟则更新位置信息
            8.toFloat(),		//位置超过8米则更新位置信息
            locationListener
        )
    }
}

4. 总结

以上就是安卓原生获取经纬度信息的全部内容,如果通过以上方法都获取不到的话,那就使用高德或百度等第三方SDK吧。

如果本文对你有帮助,请别忘记点赞start,如果有不恰当的地方也请提出来,下篇文章见。
项目地址

觉得不错还望老板打个赏呗,帮我买条秋裤穿 (*꒦ິ⌓꒦ີ)

5. 参考文章

10.14 Android GPS初涉 | 菜鸟教程 (runoob.com)