本文是作者在学习使用Google定位服务定位时的学习笔记,采用的是Kotlin语言编写。


  • 1.位置权限以及定位服务
  • 1.1 位置权限
  • 1.2 动态权限申请
  • 1.3 位置服务
  • 1.4 APP权限与定位服务设置界面
  • 2.实现定位功能
  • 2.1 设置Google定位服务
  • 2.2 获取位置信息
  • 2.3 获取位置更新
  • 3.Location经纬度转换地址信息
  • 3.1 请求API转换位置信息
  • 3.2 Geocoder类转换位置信息
  • 4.完整代码以及效果图
  • 4.1.代码
  • 4.2.效果贴图



1.位置权限以及定位服务

1.1 位置权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

两个权限都是用于定位权限,第一个是获取精确的位置,第二个用于获取大概位置,在本文中使用ACCESS_FINE_LOCATION 权限

1.2 动态权限申请

Android 6.0后对于某些权限需要进行动态申请权限,位置权限就包含在内,因此写下一个工具类用于动态申请权限

/**
 * 判断是否有权限
 */
fun Context.hasPermissions(@Size(min = 1) vararg permission: String): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true
    return permission.all {
        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
    }
}
/**
 * 申请权限(如果是Fragment 则可新增方法 Fragment.requestPermissions)
 */
fun Activity.requestPermissions(@Size(min = 1) vararg permissions: String, requestCode: Int) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
    this.requestPermissions(permissions, requestCode)
}
/**
 * 位置权限申请框 勾选禁止后不再询问 是否弹出自己的提示框
 */
fun Activity.shouldShowCustomPermissionRequestHint(permission: String): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false
    return !shouldShowRequestPermissionRationale(permission)
}

1.3 位置服务

获取位置信息除了权限,还需要打开位置服务,常用的定位方式无非采用 网络定位GPS定位,所以可以用以下方式判断是否打开了位置服务

/**
 * 判断是否打开了定位服务
 */
fun Context.LocationServiceEnable(): Boolean {
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
    return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
            locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}

1.4 APP权限与定位服务设置界面

/**
 * 跳转App设置界面
 */
fun Context.appSettingIntent(): Intent {
    return Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.parse("package:$packageName")
    }
}
/**
 * 跳转系统 位置服务 设置界面
 */
fun LocationSettingIntent(): Intent {
    return Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
}

2.实现定位功能

2.1 设置Google定位服务

在 Module的 build.gradle 文件中

implementation 'com.google.android.gms:play-services-location:15.0.1'

在 Project的 build.gradle 文件中 添加 google()

allprojects {
    repositories {
        google()
    }
}

官方文档请查看:https://developers.google.com/android/guides/setup

2.2 获取位置信息

可以通过 FusedLocationProviderClient 可得到最后已知的位置信息

class LocationActivity : AppCompatActivity() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.location_activity)
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        getLastLocation()
    }

    /**
     * 在使用的时候 需要进行权限申请
     */
    @SuppressLint("MissingPermission")
    private fun getLastLocation() {
        fusedLocationClient.lastLocation.addOnSuccessListener {
            it ?: return@addOnSuccessListener
            handleLocation(it)
        }
    }

    /**
     * 处理位置信息 location
     */
    private fun handleLocation(location: Location) {}
}

采用上述方式,并不是每次都会返回位置信息,在以下情况会返回 NULL

  • 1 未开启定位服务。
  • 2 设备不支持Google Play服务。
  • 3 设备从未记录其位置,可能是新设备或已恢复出厂设置的设备。
  • 4 设备上的Google Play服务已重新启动。

仅仅采用此方式,只有在成功,并记录了定位位置后 才会获取到位置信息。其他情况下,无法获取到位置信息。
官方文档请查看:https://developer.android.com/training/location/retrieve-current

2.3 获取位置更新

class LocationActivity : AppCompatActivity() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.location_activity)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        locationRequest = LocationRequest().apply {
            interval = 10000  //请求时间间隔
            fastestInterval = 5000 //最快时间间隔
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
        //位置信息回调
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                locationResult ?: return
                handleLocation(locationResult.lastLocation)
            }
        }

        requestLocationUpdate()
    }

    /**
     * 使用时需要检查权限
     */
    @SuppressLint("MissingPermission")
    private fun requestLocationUpdate() {
        fusedLocationClient.requestLocationUpdates(
            locationRequest, locationCallback, Looper.myLooper()
        )
    }

    private fun handleLocation(location: Location) {}
}

2.2中的方式只会进行一次位置信息请求
2.3中的方式会不断请求位置更新,在支持Google Play服务,网络能访问外网的情况下,基本都能定位成功。
想要停止请求位置更新时

/**
     * 停止获取位置更新
     */
    private fun stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

官方文档请查看:https://developer.android.com/training/location/receive-location-updates

3.Location经纬度转换地址信息

3.1 请求API转换位置信息

通过定位获取的Location对象,得到经纬度,然后作为参数,使用Http请求一个URL会返回对应的地址信息。
在此列举一个请求链接:
https://maps.googleapis.com/maps/api/geocode/json?latlng=37.422,-122.084&language=zh-CN&sensor=false
通常情况下 我们还应该加上key=YOUR_API_KEY

JSON数据很多,在此贴上部分数据的截图

android 经纬度查询位置信息 android经纬度转城市_google map


每个字段的意义请查看官方文档。

官方文档请查看:https://developers.google.com/maps/documentation/geocoding/intro

3.2 Geocoder类转换位置信息

private fun handleLocation(location: Location) {
        val geocoder = Geocoder(this, Locale.getDefault())
        Thread(Runnable {
            try {
                var addresses = geocoder.getFromLocation(
                    location.latitude, location.longitude, 1
                )
                Log.i("TAG", addresses.first().toString())
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }).start()
    }

官方文档请查看:https://developer.android.com/training/location/display-address

使用Android自带的Geocoder类可以将经纬度转换为详细的地址信息,但我在使用中经常出现异常
java.io.IOException: grpc failed
原因为:the service is not available 服务不可用 即设备不支持Geocoder

相关问题可查看链接:
https://stackoverflow.com/questions/4761052/why-is-android-geocoder-throwing-a-service-not-available-exception/4762815#4762815

https://stackoverflow.com/questions/16119130/android-java-io-ioexception-service-not-available

所以想要将Location 经纬度转换为 地址信息 应该采用Http请求Api的方式

4.完整代码以及效果图

4.1.代码

以下代码为本文涉及的代码,
项目地址:https://github.com/897532167/GoogleMapDemo

package cn.xhuww.googlemapdemo

import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.location.Location
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import cn.xhuww.googlemapdemo.utils.*
import com.google.android.gms.location.*

import kotlinx.android.synthetic.main.location_activity.*

class LocationActivity : AppCompatActivity() {
    private val locationPermission = Manifest.permission.ACCESS_FINE_LOCATION

    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.location_activity)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        locationRequest = LocationRequest().apply {
            interval = 10000  //请求时间间隔
            fastestInterval = 5000 //最快时间间隔
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                locationResult ?: return
                handleLocation(locationResult.lastLocation)
            }
        }
        requestLocationPermission()
    }

    @SuppressLint("MissingPermission")
    private fun requestLocationUpdate() {
        fusedLocationClient.requestLocationUpdates(
            locationRequest, locationCallback, Looper.myLooper()
        )
    }

    /**
     * 停止获取位置更新
     */
    private fun stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    private fun handleLocation(location: Location) {
        longitudeEditText.setText(location.longitude.toString())
        latitudeEditText.setText(location.latitude.toString())

        val url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" +
                "${location.latitude},${location.longitude}&language=zh-CN&sensor=false"
        addressTextView.text = url
        //请求网络即可
    }

    private fun requestLocationPermission() {
        when {
            hasPermissions(locationPermission) -> requestLocationService()
            else -> requestPermissions(locationPermission, requestCode = 1)
        }
    }

    private fun requestLocationService() {
        if (LocationServiceEnable()) requestLocationUpdate()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            requestLocationUpdate()
        }
    }
}

4.2.效果贴图

右侧修改模拟器地址,左侧实时获取最新的地址信息,并拼接出 经纬度转详细信息的 URL

android 经纬度查询位置信息 android经纬度转城市_android_02