前言:高德的定位sdk可以获取设备当前的详细信息,如经纬度,具体地址(省->街道)等,
本文主要使用的是定位sdk和地图sdk中的poi搜索功能(以当前位置半径多少米内的关键词搜索)
目录
[1.添加高德 Key](#1.添加高德 Key)
一、准备工作
1.注册高德的开发者账号
2.在控制台中创建应用,选择Android平台
3.给新建的应用添加一个key,拿到key值
二、项目
1.添加高德 Key
项目的 "AndroidManifest.xml" 文件中,添加如下代码:
XML
<!--用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--用于访问GPS定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--用于获取运营商信息,用于支持提供运营商信息相关的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!--用于访问网络,网络定位需要上网-->
<uses-permission android:name="android.permission.INTERNET"/>
<!--用于申请调用A-GPS模块-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<!--用于获取到poi列表后点击item后可以跳转到第三方地图软件中-->
<queries>
<package android:name="com.autonavi.minimap" /> <!-- 高德 -->
<package android:name="com.baidu.BaiduMap" /> <!-- 百度 -->
</queries>
<application
...... >
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="请输入您的用户Key"/>
<service android:name="com.amap.api.location.APSService"/>
......
</application>
2.代码混淆
在app模块中的proguard-rules.pro文件中添加一下代码
XML
#定位
-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}
-keep class com.autonavi.aps.amapapi.model.**{*;}
#搜索
-keep class com.amap.api.services.**{*;}
3.导入具体的sdk
在app模块下的build.gradle的dependencies代码块中添加以下语句,注意我这里用的很老的版本,新版两个同时导入会有冲突,并且还有些额外的操作,如隐私合规检测等,自己根据官网学吧
Groovy
//高德sdk
//定位功能
implementation 'com.amap.api:location:3.3.0'
//搜索功能
implementation 'com.amap.api:search:5.0.0'
4.地图管理类
由于我的需求只需要获取加油站和充电站,所以用了枚举,其实你可以在getPoiData()方法中的类型位置输入任何关键词的
Kotlin
//地图定位
object MapManger {
// 定义权限请求码
internal val REQUEST_CODE_LOCATION = 100
//定位客户端对象
var mLocationClient: AMapLocationClient? = null
// 定位结果回调
var locationCallback: LocationCallback? = null
//当前位置的定位信息
var locationInfo: LocationInfo? = null
//附近的加油站列表
var gasStationPoiList: ArrayList<PoiItem>? = null
//附近的充电站列表
var chargeStationPoiList: ArrayList<PoiItem>? = null
//上次的定位成功时间
var lastLocationSuccessTime = 0L
//如果定位失败后,重新定位间隔时间
var relocationTime = 5000L
fun init() {
// 初始化定位
mLocationClient = AMapLocationClient(App.app)
mLocationClient!!.setLocationOption(getDefaultOption())
// 设置定位回调监听
mLocationClient!!.setLocationListener(object : AMapLocationListener {
override fun onLocationChanged(amapLocation: AMapLocation?) {
if (amapLocation != null) {
if (amapLocation.errorCode == 0) {
stopLocation()
locationInfo = LocationInfo(
amapLocation.latitude,
amapLocation.longitude,
amapLocation.accuracy,
amapLocation.address,
amapLocation.country,
amapLocation.province,
amapLocation.city,
amapLocation.district,
amapLocation.street,
amapLocation.streetNum,
amapLocation.cityCode,
amapLocation.adCode,
amapLocation.aoiName
)
locationCallback?.success()
lastLocationSuccessTime = System.currentTimeMillis()
} else {
locationCallback?.failure()
//定位失败时,可通过ErrCode(错误码)信息来确定失败的原因,errInfo是错误信息,详见错误码表。
Log.e(
"AmapError",
"location Error, ErrCode:${amapLocation.getErrorCode()},errInfo:${amapLocation.getErrorInfo()}"
)
}
}
}
})
}
//开始定位
fun startLocation() {
try {
if (mLocationClient == null) {
init()
}
if (locationInfo == null || System.currentTimeMillis() - lastLocationSuccessTime >= 1000 * 60 * 10) {
mLocationClient!!.startLocation();
}
} catch (e: Exception) {
e.printStackTrace()
}
}
//停止定位
fun stopLocation() {
try {
mLocationClient?.stopLocation()
} catch (e: Exception) {
e.printStackTrace()
}
}
//销毁
@JvmStatic //在Java中声明为静态方法
fun destroyLocation() {
if (null != mLocationClient) {
/**
* 如果AMapLocationClient是在当前Activity实例化的,
* 在Activity的onDestroy中一定要执行AMapLocationClient的onDestroy
*/
stopLocation()
mLocationClient?.onDestroy()
mLocationClient = null
mLocationClient = null
}
}
//获取poi数据
fun getPoiData(
context: Context,
poiType: PoiType,
poiSearchListener: PoiSearch.OnPoiSearchListener
) {
val query = PoiSearch.Query(poiType.name, "", locationInfo?.cityCode);
query.setPageSize(50);// 设置每页最多返回多少条poiitem
query.setPageNum(0);//设置查询页码
val poiSearch = PoiSearch(context, query);
poiSearch.setOnPoiSearchListener(poiSearchListener)
// 设置周边搜索的中心点(经纬度)以及半径(米)
poiSearch.setBound(
SearchBound(
LatLonPoint(
locationInfo?.latitude ?: 0.0,
locationInfo?.longitude ?: 0.0
), 15000
)
);//设置周边搜索的中心点以及半径
poiSearch.searchPOIAsyn()
}
/**
* 打开系统地图选择弹窗,跳转到第三方地图的POI详情页
* @param context 上下文
* @param poi 包含以下字段的数据类:
* id: String (高德POI ID),
* name: String,
* latitude: Double,
* longitude: Double,
* city: String? (百度地图必需)
*/
fun openPoiDetail(context: Context, poi: PoiItem) {
val encodedName = URLEncoder.encode(poi.title, "UTF-8")
val encodedAddress = URLEncoder.encode(poi.snippet ?: poi.title, "UTF-8")
val mapUris = listOf(
// 高德地图(使用坐标+名称方案)
Uri.parse(
"amapuri://viewMap?" +
"sourceApplication=${context.packageName}" +
"&poiname=$encodedName" +
"&lat=${poi.latLonPoint.latitude}" +
"&lon=${poi.latLonPoint.longitude}" +
"&dev=0"
) to "com.autonavi.minimap",
// 百度地图(使用marker方案)
Uri.parse(
"baidumap://map/marker?" +
"location=${poi.latLonPoint.latitude},${poi.latLonPoint.longitude}" +
"&title=$encodedName" +
"&content=$encodedAddress" +
"&coord_type=gcj02" +
"&src=${context.packageName}"
) to "com.baidu.BaiduMap",
// 网页版高德
Uri.parse(
"https://uri.amap.com/marker?" +
"position=${poi.latLonPoint.longitude},${poi.latLonPoint.latitude}" +
"&name=$encodedName" +
"&src=${context.packageName}"
) to null,
// 网页版百度
Uri.parse(
"https://map.baidu.com/marker?" +
"location=${poi.latLonPoint.latitude},${poi.latLonPoint.longitude}" +
"&title=$encodedName" +
"&content=$encodedAddress" +
"&autoOpen=true"
) to null
)
// 过滤可用应用
val availableIntents = mapUris.mapNotNull { (uri, packageName) ->
packageName?.let {
Intent(Intent.ACTION_VIEW, uri).apply {
`package` = packageName
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.takeIf {
context.packageManager.resolveActivity(it, 0) != null
}
} ?: Intent(Intent.ACTION_VIEW, uri)
}
// 启动系统选择器
if (availableIntents.isNotEmpty()) {
context.startActivity(
Intent.createChooser(
availableIntents.first(),
"在地图中查看"
).apply {
putExtra(Intent.EXTRA_INITIAL_INTENTS, availableIntents.drop(1).toTypedArray())
}
)
} else {
Toast.makeText(context, "未找到可用地图应用", Toast.LENGTH_SHORT).show()
}
}
//获取定位参数配置
private fun getDefaultOption(): AMapLocationClientOption {
val mOption = AMapLocationClientOption()
// 可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
mOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
// 可选,设置是否gps优先,只在高精度模式下有效。默认关闭
// mOption.isGpsFirst = false
// 可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效
mOption.httpTimeOut = 10000
// 可选,设置定位间隔。默认为2秒
// mOption.interval = 2000
// 可选,设置是否单次定位。默认是false
mOption.isOnceLocation = true
// 可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用
// mOption.isOnceLocationLatest = true
// 可选,设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP
// AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP)
// 可选
// mOption.isSensorEnable = false
// 可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
// mOption.isWifiScan = true
// 可选,设置是否使用缓存定位,默认为true
mOption.isLocationCacheEnable = false
// 可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言)
// mOption.geoLanguage = AMapLocationClientOption.GeoLanguage.DEFAULT
return mOption
}
//检查所需权限
fun checkPermissions(): Boolean {
val permissions = getRequiredPermissions()
return if (permissions.isNotEmpty()) false else true
}
fun getRequiredPermissions(): Array<String> {
val permissions = mutableListOf<String>()
if (!checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (!checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
}
return permissions.toTypedArray()
}
// 检查单个权限
fun checkPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
App.app as Context,
permission
) == PackageManager.PERMISSION_GRANTED
}
data class LocationInfo(
val latitude: Double, // 纬度
val longitude: Double, // 经度
val accuracy: Float, // 精度
val address: String?, // 地址(可能为null)
val country: String?, // 国家
val province: String?, // 省份
val city: String?, // 城市
val district: String?, // 区县
val street: String?, // 街道
val streetNum: String?, // 门牌号
val cityCode: String?, // 城市编码
val adCode: String?, // 区域编码
val aoiName: String? // AOI信息
)
interface LocationCallback {
fun success()
fun failure()
}
enum class PoiType {
加油站,
充电站
}
}
6.在activity/fragment中使用
下面是在fragment中使用,只需要把requireActivity(),requireContext()等获取上下文的方法替换为this就行
Kotlin
var mapManger = MapManger
override fun onResume() {
super.onResume()
if (mapManger.checkPermissions()) {
mapManger.startLocation()
} else {
//弹出权限申请弹窗,点击确定按钮去动态申请权限,这里直接申请
ActivityCompat.requestPermissions(
requireActivity(),
mapManger.getRequiredPermissions(),
mapManger.REQUEST_CODE_LOCATION
)
}
}
//activity的话在onCreate()中
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapManger.locationCallback = object : LocationCallback {
override fun success() {
//定位成功,获取数据
getPoiData()
}
override fun failure() {
//重新定位
Handler().postDelayed({
mapManger.relocationTime = mapManger.relocationTime*1.5.toLong()
mapManger.startLocation()
}, mapManger.relocationTime)
}
}
//如果你有两个界面功能相似,像一个界面是加油站一个是充电站的话,由于mapManger是单例类,全局用一个对象,如果别的界面进行了定位,这个界面将不会收到回调,重新拉一次自己需要的数据
if (mapManger.locationInfo!= null && mapManger.gasStationPoiList == null){
//说明在别的界面进行了定位,当前界面不会触发定位成功回调,手动一次获取数据
getPoiData()
}
}
fun getPoiData(){
binding.cityNameTv.text = mapManger.locationInfo?.city
mapManger.getPoiData(
requireContext(),
MapManger.PoiType.加油站,
object : OnPoiSearchListener {
override fun onPoiSearched(poiResult: PoiResult?, p1: Int) {
val list = poiResult?.getPois()
if (!list.isNullOrEmpty()) {
binding.noDataFl.visibility = View.GONE
mapManger.gasStationPoiList = list
//这里已经获取到列表,可以将数据填充到recyclerview中
//如果要点击某项item进行跳转的话,调用mapManger.openPoiDetail()方法就行
}
}
override fun onPoiItemSearched(p0: PoiItem?, p1: Int) {}
})
}
此外还应该在activity的onDestroy中调用回收方法,以回收资源,防止内存泄漏
Kotlin
MapManger.destroyLocation()
7.PoiItem类的属性解释
Kotlin
poiId //poi的ID
title //poi名称
adName //所在区的名称
snippet //区以下的详细地址 街道等
distance //距离(单位米)
photos //图片列表
tel //电话号
latLonPoint //经纬度 获取单个经度/纬度 :latLonPoint.latitude latLonPoint.longitude
//其余还有一些 像 省份、城市、邮编等信息不再列举了,和管理类里的LocationInfo对象属性应该差不多