安卓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对象属性应该差不多
相关推荐
用户2018792831676 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子6 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82276 小时前
安卓接入Max广告源
android
齊家治國平天下6 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO6 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel6 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢6 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖6 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉8 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~8 小时前
【Android】浅谈androidx.startup.InitializationProvider
android