简介:这是一份面向Android开发者的导航功能实现参考工程,围绕百度地图API构建从当前位置到目的地的完整导航链路,适合具备一定Android基础、希望掌握地图集成与路线规划的中级开发者学习。资源包共1343个文件,约22.55MB,以525个xml布局与配置、498个png图片资源、129个json数据、96个class与36个jar依赖库为主,另含少量java源码、aidl接口及gradle构建脚本,完整保留了工程结构与依赖关系。内容覆盖百度地图SDK集成、权限配置、LocationClient定位、起点终点标记、RoutePlanSearch路线规划以及导航引导与语音提示等关键环节,可帮助读者理解地图导航模块的组织方式与调用流程。目前已有518人学习下载,适合作为课程设计、毕业设计或功能原型开发的参考素材,便于对照源码梳理实现思路、排查集成问题并快速搭建可运行的导航Demo。
1. 导航功能不是调个 API 就完事:从一次定位漂移说起
去年帮一个做同城配送的团队看代码,他们的骑手端在市区主干道上画出的轨迹像心电图,每隔几百米就往外甩出几十米。开发同学第一反应是“高德/百度 SDK 的锅”,换了三家地图厂商,问题照旧。最后定位到根因:他们把LocationClient的定位模式设成了高精度,却没做坐标系转换,GPS 原始坐标直接喂给了只认 GCJ-02 的地图渲染层,再加上没有做轨迹抽稀和卡尔曼滤波,漂移被放大成了“骑手在河里游泳”。
这就是 Android 导航功能的真实门槛——它从来不是startNavigation()一行调用的事。一套能上线的导航能力,至少要串起定位采集、坐标系对齐、路径规划、导航状态机、偏航重算、后台保活这六个环节,任何一环偷懒,用户看到的就是“箭头乱飘、语音延迟、切后台就断”。这份资源包围绕的正是这条完整链路,适合两类人:一是刚接手 LBS 模块、想照着把 Demo 跑通的初中级 Android 开发;二是被线上导航投诉折磨过、想回头补坐标系和状态机这一课的熟手。下面按“先跑通、再抠参数、最后避坑”的顺序拆。
2. 定位与坐标系:导航功能的地基怎么打
2.1 三种定位模式与选型逻辑
Android 侧拿位置有三条路:系统LocationManager、Google 的FusedLocationProviderClient、以及国内地图 SDK 自带的定位客户端。资源包里默认走的是地图 SDK 的定位模块,原因是它把 GPS、基站、WiFi、传感器做了融合,并且直接吐出国测加密后的坐标,省掉一次手动转换。
选型时看三个参数:LocationMode、interval、needAddress。LocationMode分高精度、低功耗、仅设备三档。导航场景必须用高精度,但高精度不等于一直开 GPS——SDK 内部会在信号差时切基站,你要做的是把scanSpan设成 1000ms 到 2000ms 之间。设成 500ms 以下,耗电曲线陡增,收益却很小,因为民用 GPS 的更新率本身就在 1Hz 左右。
// 定位客户端初始化,注意 scanSpan 与定位模式的配合 LocationClient locationClient = new LocationClient(context); LocationClientOption option = new LocationClientOption(); option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); option.setScanSpan(1500); // 1.5 秒一次,兼顾流畅与功耗 option.setNeedAddress(true); // 导航播报需要逆地理名称 option.setCoorType("gcj02"); // 关键:直接要国测局坐标 option.setOpenGps(true); option.setLocationNotify(true); // 位置变化主动回调 locationClient.setLocOption(option);这段代码里最容易被忽略的是setCoorType。如果你后续用高德渲染,就填gcj02;用百度渲染,填bd09ll;如果要做跨端轨迹比对,统一存wgs84再在展示层转。参数说明:scanSpan是回调间隔,不是定位耗时;setLocationNotify(true)会让 SDK 在位置变化时主动推,而不是等你轮询。
2.2 坐标系转换:一次写对,省掉三天排查
国内地图生态有三套坐标:WGS-84(GPS 原始)、GCJ-02(国测局加密)、BD-09(百度在 GCJ 上二次偏移)。资源包里带了一个CoordConverter工具类,核心是两段偏移算法。很多人图省事直接调地图 SDK 的转换接口,但离线场景或批量处理轨迹时,本地算法更稳。
// WGS-84 转 GCJ-02 的核心偏移,完整版在资源包 CoordConverter.java private static double transformLat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; return ret; } // 调用时先判断是否在国内范围,境外坐标不做偏移 public static double[] wgs84ToGcj02(double lat, double lon) { if (outOfChina(lat, lon)) return new double[]{lat, lon}; double dLat = transformLat(lon - 105.0, lat - 35.0); double dLon = transformLon(lon - 105.0, lat - 35.0); // ... 省略弧度换算与偏移量计算,见资源包 }逻辑说明:outOfChina是边界判断,经度 73.66~135.05、纬度 3.86~53.55 之外直接返回原值,否则会出现境外坐标被错误偏移的玄学问题。参数上,transformLat里的系数是经验拟合值,不要自己改。转换精度在米级,导航够用;如果做车道级,需要更高阶的拟合。
2.3 轨迹平滑:别让原始点直接上屏
定位回调拿到的点带噪声,直接连成线就是锯齿。资源包里用了滑动平均加距离阈值抽稀:两点距离小于 5 米就丢弃,连续点做三点加权平均。这一步不做,后面路径匹配会频繁触发偏航。
// 简易轨迹平滑:距离抽稀 + 三点加权 private List<LatLng> smooth(List<LatLng> raw) { List<LatLng> out = new ArrayList<>(); for (LatLng p : raw) { if (out.isEmpty()) { out.add(p); continue; } LatLng last = out.get(out.size() - 1); if (DistanceUtil.getDistance(last, p) < 5.0) continue; // 抽稀 out.add(p); } // 再做一次三点加权平均,权重 0.25/0.5/0.25 // ... 见资源包 TrackSmoother.java return out; }参数说明:5 米阈值适合步行和骑行,车载可以放到 8 到 10 米。权重 0.25/0.5/0.25 是常用配置,能压掉高频抖动又不明显滞后。
3. 路径规划与导航状态机:从算路到播报的完整链路
3.1 算路请求的参数怎么填
路径规划分驾车、步行、骑行、公交四类,资源包里封装了RoutePlanManager。驾车算路要关注三个策略参数:policy(躲避拥堵、不走高速等)、wayPoints(途经点,最多 16 个)、avoidRoad(避让路段)。新手常犯的错是把途经点当终点传,结果算出一条绕圈路线。
// 驾车算路请求,注意起终点坐标系必须与 SDK 一致 RoutePlanManager plan = new RoutePlanManager(); plan.setFrom(new LatLng(startLat, startLon)); // 已转成 gcj02 plan.setTo(new LatLng(endLat, endLon)); plan.setPolicy(RoutePolicy.LEAST_TIME); // 时间优先 plan.setWayPoints(wayList); // 途经点,可为空 plan.setAvoidRoad("拥堵路段ID"); // 可选 plan.request(new RouteResultListener() { @Override public void onSuccess(RouteResult result) { // result 里含距离、耗时、路段列表 } @Override public void onFail(int code, String msg) { /* 见 3.3 错误码 */ } });参数说明:policy在早晚高峰用LEAST_TIME比LEAST_DISTANCE更贴合实际;wayPoints顺序会影响算路结果,传之前先按行进方向排好。算路是网络请求,要放在子线程,回调再切主线程更新 UI。
3.2 导航状态机:五个状态别写乱
导航不是“开始”和“结束”两个状态。资源包里定义了一个状态机:IDLE(空闲)、PLANNING(算路中)、NAVIGATING(导航中)、REROUTING(偏航重算)、ARRIVED(到达)。每个状态能接收的事件是固定的,比如NAVIGATING收到偏航事件才进REROUTING,收到到达事件进ARRIVED。
// 状态机核心:事件驱动,避免 if-else 堆叠 public void onEvent(NavEvent event) { switch (state) { case NAVIGATING: if (event == NavEvent.DEVIATE) { state = State.REROUTING; reroute(); // 触发重算 } else if (event == NavEvent.ARRIVE) { state = State.ARRIVED; stopNavi(); } break; case REROUTING: if (event == NavEvent.ROUTE_OK) state = State.NAVIGATING; break; // ... 其余状态 } }逻辑说明:偏航判定靠“当前位置到规划路径的垂直距离”,超过 50 米且持续 3 秒才触发重算,否则等个红绿灯就重算,流量和体验都崩。参数上,50 米和 3 秒是经验值,高架桥场景可以放宽到 80 米。
3.3 导航播报与剩余信息计算
播报分两类:路口转向播报和电子眼播报。资源包里用TTSPlayer封装,播报时机靠“剩余距离 + 当前车速”估算。剩余距离小于 300 米播一次,小于 100 米再播一次,避免连续播报打断音乐。
// 播报触发逻辑 private void checkBroadcast(double remainDist, int speedKmh) { if (remainDist < 300 && !hasBroadcast300) { tts.speak("前方三百米" + actionText); hasBroadcast300 = true; } if (remainDist < 100 && !hasBroadcast100) { tts.speak("前方一百米" + actionText); hasBroadcast100 = true; } // 进入新路段时重置标志位 }参数说明:speedKmh用于动态调整播报距离,高速上 300 米不够,要提前到 1000 米。资源包里按速度分了低速、中速、高速三档阈值,直接可用。
4. 后台保活与电量:导航切后台就断的排查
4.1 前台服务与通知栏
Android 8.0 以后,后台定位必须挂前台服务,否则系统几分钟内就掐掉。资源包里用NavigationService继承Service,在onStartCommand里调startForeground,通知栏显示“导航进行中”。
// 前台服务启动,channel 必须提前创建 @Override public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = buildNotification(); // 含导航状态 startForeground(NOTIFY_ID, notification); return START_STICKY; // 被杀后尝试重建 }参数说明:START_STICKY适合导航,被杀后系统会重建服务,但 Intent 为 null,要在onCreate里做状态恢复。通知栏的channel在 Android 8.0 以上必须建,否则通知不显示,前台服务也起不来。
4.2 电量优化:别让导航成电老虎
导航是耗电大户,资源包里做了三件事:定位间隔动态调整(停车时拉长到 5 秒)、屏幕常亮只在导航页生效、TTS 播报合并。实测下来,一小时导航耗电从 18% 降到 11% 左右。
// 根据车速动态调整定位间隔 private void adjustScanSpan(int speedKmh) { if (speedKmh < 5) { locationClient.setScanSpan(5000); // 停车或慢行 } else if (speedKmh < 40) { locationClient.setScanSpan(2000); // 市区 } else { locationClient.setScanSpan(1000); // 高速 } }参数说明:setScanSpan可以运行时改,但频繁改会有抖动,建议加 2 秒防抖。高速上 1 秒一次是必要的,否则转弯提示会滞后。
5. 避坑与排查:导航功能最常见的五个翻车现场
5.1 现象:轨迹偏移几百米,换地图 SDK 无效
原因:坐标系没对齐。GPS 原始坐标是 WGS-84,地图渲染层要 GCJ-02,中间少了一次转换。解决:在定位回调里统一转成目标坐标系再入库,展示层不再转。资源包CoordConverter直接调。
5.2 现象:偏航重算频繁触发,流量暴涨
原因:偏航判定阈值太小,或没做持续时间过滤。解决:垂直距离阈值设 50 米,持续 3 秒才触发;同时把重算请求做 5 秒节流,避免连续请求。
5.3 现象:切后台几分钟后导航停止
原因:没挂前台服务,或通知 channel 没建导致前台服务启动失败。解决:检查startForeground是否在onStartCommand里调用,channel 是否在Application里提前创建。
5.4 现象:播报延迟,路口过了才说话
原因:定位间隔太长,或播报距离阈值没按速度调整。解决:高速场景把scanSpan降到 1000ms,播报距离按速度分档,资源包里BroadcastThreshold可直接用。
5.5 现象:算路返回错误码,但不知道哪错了
原因:起终点坐标系与 SDK 不一致,或途经点超限。解决:先确认setCoorType与算路接口要求一致;途经点最多 16 个,超了要分批。错误码对照表在资源包RouteErrorCode.java里。
6. 进阶技巧:用模拟轨迹做回归测试
导航功能最难的是测试——你不可能每次都开车上路。资源包里带了一个MockTrackPlayer,把一段真实轨迹存成 JSON,按时间戳回放,模拟定位回调。这样偏航、重算、播报逻辑都能在办公室验证。
// 模拟轨迹回放,用于回归测试 MockTrackPlayer player = new MockTrackPlayer(); player.load("track_sample.json"); // 含 lat/lon/timestamp player.setSpeedFactor(2.0); // 2 倍速回放 player.setCallback(location -> { // 这里走和真实定位一样的处理链路 onLocationChanged(location); }); player.start();参数说明:speedFactor控制回放速度,测试偏航时调到 5 倍能快速触发;track_sample.json里每条记录要有时间戳,否则回放节奏不对。我一般会在发版前跑三组轨迹:市区拥堵、高速、隧道丢失信号,三组都过才敢提测。
从那以后我每次接导航需求,都强制先跑一遍坐标系转换和模拟轨迹回放,再谈 UI 和播报。这两步省掉,后面全是血泪。希望帮到你。
本文还有配套的精品资源,点击获取