ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android集成高德地图定位导航:SDK接入与源码工程化实践

2026/9/12 22:01:18 拓冰建站 浏览量
Android集成高德地图定位导航:SDK接入与源码工程化实践 简介基于安卓平台开发的高德地图定位与导航功能源码包面向需要集成地图能力的移动应用开发者及毕设实训学员重点解决定位信息获取与多方式路线导航实现难题。定位模块展示如何获取经纬度、海拔等关键数据并借助高德地图接口精准确定用户位置导航模块从路线规划逻辑到导航界面呈现均有完整实现支持驾车、步行、骑行等出行方式可依据起点、终点及实时交通状况智能规划最优路径配合地图指引线与语音提示引导用户行进。压缩包共一百个文件以so动态库、xml布局、png图标为主辅以gradle构建配置、jar依赖、java源码等整体约19.18MB文件组织贴近安卓工程结构便于对照学习。已有273人浏览学习适合有一定安卓基础、希望快速上手地图类应用开发的工程师与毕业生参考复用。1. 基于 Android 实现的高德地图定位导航源码拿到手第一步做什么很多人从网上下载“基于Android实现的高德地图定位以及导航功能源码.zip”解压后看到十几个模块和一屏报错第一反应是换个版本重下。其实这类源码跑不起来的根因多半不是代码本身而是高德 Key、签名和权限这三件套没配好。这里按平时接入高德定位与导航的顺序来讲先解决 SDK 接入和 Key 配置再拆定位与导航两条主线最后落到源码工程化和瓦片缓存这类容易被忽略的细节。适合手里有源码但跑不起来的人也适合想从零把高德定位导航集成进自己项目的 Android 开发者要改车机版或做定制导航 UI 的人也能从这里找到可动的边界。2. 高德地图 Android SDK 接入Key、依赖与权限一次配齐2.1 申请 Key 前先搞懂 SHA1 和包名的绑定关系高德地图开放平台的 Key 不是随意粘贴的字符串它必须和你应用的包名、签名证书的 SHA1 一一对应。控制台创建应用时填的包名要和 build.gradle 里的 applicationId 完全一致填写的 SHA1 必须来自最终打包实际使用的 keystore。很多人 debug 包能定位、release 包总是鉴权失败就是因为控制台只填了 debug 的 SHA1。获取调试签名的 SHA1 用下面这条命令keystore 默认在用户目录下的 .android 目录里keytool -v -list -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android拿到 SHA1 后在开放平台创建应用把包名、SHA1 填入就能生成 Key。对下载下来的源码需要全局搜索旧的 Key 并替换成自己的。注意替换的不只是 AndroidManifest.xml 里的 meta-dataAMapServices.setApiKey和导航相关配置也同样要改否则会出现地图能显示但导航鉴权失败这种半通不通的现象。有些源码把 Key 写死在 build.gradle 的 buildConfigField 里如果只改了配置文件而没改 build.gradle编译出来的包带的是旧 Key运行时才暴露。2.2 三个 SDK 的依赖怎么选版本别混用高德在 Android 侧分三个 SDK3D 地图、定位、导航。导航 SDK 本身依赖地图显示能力所以导航场景通常要同时引入 3dmap 和 navi-3dmap定位 SDK 则相对独立。SDKGradle 依赖负责的能力3D 地图com.amap.api:3dmap地图显示、Marker、瓦片加载定位com.amap.api:location经纬度获取、逆地理编码导航com.amap.api:navi-3dmap路线规划、导航组件、语音播报版本号最好集中写在 gradle 文件里避免三个 SDK 版本漂移。高德 SDK 的前后兼容做得一般导航版本和地图版本差距过大会在运行期抛出类找不到或直接黑屏。下面是一组常见组合dependencies { implementation com.amap.api:3dmap:9.8.2 implementation com.amap.api:location:6.4.7 implementation com.amap.api:navi-3dmap:9.8.2 implementation com.amap.api:search:9.7.2 }search 不是必须的但关键字搜索、POI 搜索会用到。如果源码里出现搜索页面而依赖里没有 search 包运行到搜索功能时会抛 NoClassDefFoundError。另外要考虑 ABI 过滤三个 SDK 的 so 文件加起来体积不小需要在 defaultConfig 里限制 CPU 架构。arm64-v8a 是主流要兼容老平板就保留 armeabi-v7a模拟器调试再加 x86。2.3 Manifest 权限与初始化时序权限声明直接决定定位是否有效。下面这组权限覆盖定位、导航、后台记录三类场景。后台定位在国产 ROM 上多数默认关闭需要用户手动去电池设置里允许。uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_LOCATION /Android 6.0 以上部分权限需要运行时动态申请光在 manifest 里写着没用。Android 12 之后前台服务必须搭配 foregroundServiceTypelocation 才能正常启动导航服务。如果源码没有做动态权限和前台服务适配导航语音播报开始后容易被系统回收表现为导航界面还在但位置不动、语音中断。初始化位置建议放在 Application 的 onCreateoverride fun onCreate() { super.onCreate() AMapServices.init(this) AMapServices.setApiKey(你的Key) }这里的关键点是必须在任何地图或导航界面创建之前初始化。有人把初始化写在 MainActivity 的 onCreate 里然后通过路由跳转到第二个页面使用地图第一次进入没问题冷启动或者返回再进就直接崩。地图控件 MapView、导航控件 AMapNaviView 的生命周期方法需要和 Activity 同步尤其 onDestroy 里的清理写漏了会出现“地图越用越卡”的现象。3. 定位功能实现三种定位模式与高频错误码排查3.1 封装一个可复用的 Locator避免到处 new定位功能的核心是 AMapLocationClient。网上很多源码是页面里怎么用就怎么 new导致两个问题一是每次进入页面都重新申请定位授权弹窗反复出现二是页面销毁时 client 没销毁后台持续定位耗电。我一般会封装一个偏业务层的 Locator。object Locator { private var client: AMapLocationClient? null fun start(context: Context, listener: (AMapLocation) - Unit) { client AMapLocationClient(context) val option AMapLocationClientOption() option.locationMode AMapLocationClientOption.AMapLocationMode.Hight_Accuracy option.isOnceLocation false option.isNeedAddress true option.isMockEnable false option.interval 2000 client?.setLocationOption(option) client?.setLocationListener(listener) client?.startLocation() } fun stop() { client?.stopLocation() client?.onDestroy() client null } }这个封装里几个参数值得单独说。locationMode 有三个取值Hight_Accuracy 是 GPS 加网络混合定位室内室外都有结果是绝大多数场景的默认选择Battery_Saving 只走 Wi-Fi 和基站省电但误差在几十米到几百米Device_Sensors 是纯 GPS适合越野、轨迹这类场景冷启动要等卫星锁定经常超过十秒。interval 是连续定位的周期单位毫秒。定位不是 interval 设得越小越准高德服务端对高频请求有限流。源码里如果看到 interval 填 500大概率是演示用上线前要改成 2000 或以上。isMockEnable 控制是否接受模拟定位。测试时用虚拟定位软件模拟到某个地点发现一直失败是因为它默认关闭。反过来做外勤签到并希望防作弊就保持 false再配合服务端对坐标的重复性校验。3.2 回调里的经纬度坐标系和逆地理编码AMapLocationListener 回调里getLatitude、getLongitude 已经是 GCJ-02 坐标系。它和高德地图瓦片是匹配的直接画点不会偏。但如果服务端用的是 WGS-84 原始坐标上传前需要做一次转换否则服务端做围栏计算时会出现几百米偏差。源码里如果看到 CoordinateUtils通常做的就是 GCJ-02 和 WGS-84 互转。isNeedAddress 设为 true 后AMapLocation 自带省市区街道和门牌号直接 getAddress 就能拿到。很多源码在拿到定位结果后又调一次 GeocodeSearch 逆地理编码这是多此一举。逆地理接口有 QPS 限制频繁调用会触发频控结果就是定位回调正常但业务功能慢半拍。client?.setLocationListener { loc - if (loc.errorCode 0) { val lat loc.latitude val lng loc.longitude val address loc.address Log.d(Locator, 定位成功: $lat, $lng, $address) } else { Log.e(Locator, 定位失败: ${loc.errorCode}, ${loc.errorInfo}) } }日志里一定要打 errorInfo它比 errorCode 更细。比如错误码 7 在控制台解释是 KEY 鉴权失败但具体是包名不符还是 SHA1 不符errorInfo 会说清楚。3.3 定位失败时先看这三样再翻错误码表定位失败不要急着改代码先排查环境。第一步手机定位服务开关是否打开第二步应用是否有精确定位权限部分手机用“模糊定位”会产生几十米误差第三步确认 Key 对应的包名和签名就是当前调试用的。这三项过完再看错误码。错误码含义排查方向0成功无4协议解析错误网络异常或 SDK 版本过旧7KEY 鉴权失败包名、SHA1、Key 不一致12缺少定位权限定位开关或运行时权限未开启13网络定位失败当前网络不可用稍后重试18鉴权失败初始化前没有设置 Key源码调试时还有一个容易被忽略的点如果测试机同时装着旧版测试包和新版正式包两个包包名相同但签名不同定位服务可能被旧的抢占导致新包一直失败。卸载旧包再试一次往往就好了。4. 导航功能实现路线规划、NaviView 接入与模拟导航调试4.1 先算路再导航RouteSearch 的异步回调高德的导航不是一个 start 方法就能跑。正规流程是先调路线规划拿到一条或多条路线再开始导航。路线规划用 RouteSearch整个过程异步执行需要注册监听回调。val search RouteSearch(context) search.setRouteSearchListener(object : RouteSearch.OnRouteSearchListener { override fun onDriveRouteSearched(result: DriveRouteResult?, code: Int) { if (code 1000 result ! null) { val route result.paths[0] // route.duration 预计耗时route.distance 总距离 val navi AMapNavi.getInstance(context) navi.calculateDriveRoute( route.waypoints.map { it as RouteSearch.LatLonPoint }, listOf(RouteSearch.LatLonPoint(endLat, endLng)), emptyList(), AMapNavi.DrivingDefault ) } } }) val from RouteSearch.LatLonPoint(startLat, startLng) val to RouteSearch.LatLonPoint(endLat, endLng) val query RouteSearch.DriveRouteQuery(from, to, RouteSearch.DrivingDefault, emptyList(), ) search.calculateDriveRouteAsyn(query)这段比直接开导航复杂但更贴近真实项目。DriveRouteQuery 倒数第二个参数是途经点列表最后是路线偏好空字符串表示默认。算路结果里的每一条 path 都包含距离、时长、收费金额和红绿灯数量。做路线选择的源码通常在回调里把多条 path 显示成列表让用户挑而不是只取第一条。路线规划偏好是导航体验差异最大的参数。策略值含义DrivingDefault0默认综合考虑时间和距离DrivingSaveMoney3避免收费DrivingShortDistance5距离最短DrivingAvoidCongestion4躲避拥堵DrivingNoHighWay2避开高速避开高速和距离最短经常让路线绕远路实际驾车体验可能更差。做企业服务时建议让用户在设置里选择而不是写死。4.2 在布局里放 AMapNaviView生命周期与启动模式自带导航组件 AMapNaviView 接管了绝大部分 UI。转向箭头、道路名、重新规划提示都不用自己画定制按钮和换肤操作都基于它来做。布局文件里两行com.amap.api.navi.AMapNaviView android:idid/navi_view android:layout_widthmatch_parent android:layout_heightmatch_parent /Activity 里要设置监听器并在对应生命周期里传递事件。class NaviActivity : AppCompatActivity(), AMapNaviViewListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_navi) val naviView: AMapNaviView findViewById(R.id.navi_view) naviView.setAMapNaviViewListener(this) AMapNavi.getInstance(this).startNavi(AMapNavi.GPSNaviMode) } override fun onResume() { super.onResume() naviView.onResume() } override fun onDestroy() { super.onDestroy() naviView.onDestroy() AMapNavi.getInstance(this).destroy() } }GPSNaviMode 是真实导航模拟导航用 EmulatorNaviMode。调试源码时建议先用模拟导航把所有转弯播报、偏航重算逻辑跑一遍。模拟导航不依赖真实 GPS交互状态与真实导航几乎一致可以在工位上直接测连续导航十分钟观察有没有内存上涨、播报卡顿。很多源码这里会漏两个配置。一个是 AMapNaviView 的懒加载模式setLazyEnable(false) 可以让页面一进来就加载地图避免切换页面后白屏代价是页面打开稍慢。另一个是 AMapNavi 全局单例每进一次导航页面重新获取但 onDestroy 里忘记 destroy第二次进入就会出现定位漂移和语音播报叠加。4.3 导航语音、前台服务与偏航重算的参数导航语音默认是静音的需要显式开启AMapNavi.getInstance(this).setUseInnerVoice(true)如果震动正常但一直没有语音优先检查 TTS 初始化是否完成。TTS 初始化比较耗时放到 Application 里做比放导航页面更稳。语音播报开始后建议同步启动前台服务配合 Android 12 的 foregroundServiceType 限制否则导航过程中息屏一段时间进程可能被系统回收。偏航重算默认开启。用户偏离路线时SDK 调用 onReCalculateRoute 回调并自动重新规划。这个行为不建议关闭但提示音在高速上容易和导航播报混在一起。做车机版改造时一般把重新计算的回调接到 UI 上显示“已为您重新规划路线”再配合 SDK 默认语音体验会清楚很多。5. 源码工程化拆分包结构、瓦片缓存与离线包落地5.1 拿到源码 zip 后先重建包结构从 zip 解压出来的代码包名和类结构可能比较乱。我会先把和地图相关的代码统一收拢到一个包下让替换地图厂商时有一个清晰的换入换出点。参考结构如下com.example.navi/ ├── api/ │ ├── LocationManager.java │ ├── RouteManager.java │ └── NaviStarter.java ├── ui/ │ ├── MainActivity.java │ ├── SearchActivity.java │ └── NaviActivity.java ├── config/ │ └── AmapConfig.java └── util/ ├── CoordinateUtils.java └── PermissionUtils.javaapi 包下设三个类分别封装定位、算路和导航启动界面层只和这三个类打交道。以后从高德切换到其他地图时界面代码基本不动只需重写 api 包实现。AmapConfig 集中放 Key、日志开关、模拟定位开关避免整个项目里散落一堆常量。5.2 瓦片缓存与离线包高德地图瓦片默认缓存在应用私有目录但容量有限。首次加载某个城市后再进入会快很多清除应用数据后第一次打开瓦片要重新加载缩放和拖动时有明显空白闪烁。车辆行驶到未缓存区域瓦片一片一片出现体验很差。源码里常见做法是在设置页提供城市离线包下载。高德官方 SDK 支持下载城市离线包下载完成后地图显示和省流量效果提升明显。注意离线包只对地图显示有效算路和实时路况仍然需要网络。后端接口加一层瓦片缓存代理是另一个思路但高德瓦片 URL 有签名参数缓存时要注意过期时间。5.3 用日志开关和 Mock 数据验证改动改完源码后最快的验证路径是打开高德 SDK 日志开关在 Application 初始化里加一行AMapServices.setEnableLog(true)日志打开后会输出 Key 鉴权、网络请求、定位回调等中间过程。Logcat 的 tag 定位到 Amap 相关前缀能看到 SDK 内部定位请求的返回结果比在业务代码里堆 Log 更接近问题源头。最后用本地 Mock 数据把路线规划 UI 和后端对接流程先跑通再换真机定位做最终验证。Mock 数据建议放在 assets 目录用 JSON 文件模拟一次算路响应这样无论 Key 是否生效都能先检查页面渲染和跳转逻辑。本文还有配套的精品资源点击获取