本文是作者在学习使用Google定位服务定位时的学习笔记,采用的是Kotlin语言编写。
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数据很多,在此贴上部分数据的截图
每个字段的意义请查看官方文档。
官方文档请查看: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/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