安卓Kotlin接入高德定位和地图SDK

前言:高德的定位sdk可以获取设备当前的详细信息,如经纬度,具体地址(省->街道)等,

本文主要使用的是定位sdk和地图sdk中的poi搜索功能(以当前位置半径多少米内的关键词搜索)

目录

一、准备工作

1.注册高德的开发者账号

2.在控制台中创建应用,选择Android平台

3.给新建的应用添加一个key,拿到key值

二、项目

[1.添加高德 Key](#1.添加高德 Key)

2.代码混淆

3.导入具体的sdk

4.地图管理类

6.在activity/fragment中使用

7.PoiItem类的属性解释


一、准备工作

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对象属性应该差不多
相关推荐
时光少年1 分钟前
Android 副屏录制方案
android·前端
时光少年11 分钟前
Android 局域网NIO案例实践
android·前端
alexhilton29 分钟前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
流浪汉kylin35 分钟前
Android TextView SpannableString 如何插入自定义View
android
火柴就是我2 小时前
git rebase -i,执行 squash 操作 进行提交合并
android
你说你说你来说3 小时前
安卓广播接收器(Broadcast Receiver)的介绍与使用
android·笔记
你说你说你来说3 小时前
安卓Content Provider介绍及使用
android·笔记
RichardLai883 小时前
[Flutter学习之Dart基础] - 类
android·flutter
_一条咸鱼_4 小时前
深度解析 Android MVI 架构原理
android·面试·kotlin
火柴就是我4 小时前
git rebase -i 修改某次提交的message
android