百度地图Android SDK在线离线双模式实时轨迹绘制实践
发布时间:2026/9/8 21:17:10 锦皓数字建站

简介基于百度地图的在线/离线轨迹绘制源码包面向需要实现地图轨迹可视化的Web/移动端开发者解决车辆运行路径实时显示及车头朝向动态跟随问题。源码演示了如何接入百度地图、绘制平滑轨迹线并通过角度偏移设置让车头图标始终指向行驶方向适配导航、物流监控与车队管理等多种场景。压缩包共2000个文件大小120.47MB以png地图瓦片、JavaScript脚本、CSS样式表为主辅以少量gif图标、字体文件及运行库dll结构完整可直接部署。包内还配有前端界面组件与地图交互模块如搜索窗口、绘制管理等便于二次开发时快速整合离线瓦片包可直接用于内网环境方便开发与演示。目前已有3853人学习下载。读者可获得完整工程源码与离线地图资源既能直接在项目中复用又能参考其坐标处理、图标旋转、离线资源加载与角度修正思路。借助这套源码还可熟悉百度地图JavaScript API的常见交互写法掌握信息弹窗、覆盖物管理等周边功能从而缩短地图类项目开发周期。1. 在线/离线双模式到底在解决什么真实问题我最早做轨迹类App的时候第一版只接了在线地图。当时觉得很简单地图SDK拉下来定位回调一接Polyline一画完事。直到有一次我背着设备去郊区实测直接翻车——山里基站稀疏4G信号时有时无地图瓦片加载不出来底图一片灰白轨迹线悬空挂在空荡荡的屏幕上那种体验感只能用四个字形容彻底完蛋。所以后来再做这类需求我基本默认一个原则凡是涉及户外移动场景的地图应用离线能力不是加分项而是刚需。你打开百度地图App切到飞行模式再试定位依然能走、轨迹依然能画靠的就是离线地图包加本地IDL定位计算。这篇文章要讲的就是如何用百度地图Android SDK同时打通在线和离线两条路实现车辆、骑行、步行场景下实时轨迹的绘制并且把完整的思路和踩坑记录留给你。有一点先说清楚在线和离线在轨迹绘制这个需求里真正有区别的地方并不在“画线”本身而在于三点——底图数据从哪来、坐标偏移怎么处理、地图引擎的生命周期怎么管。把这三点想透了源码实现只是一个组装问题。整篇文章我会按环境准备、定位采集、轨迹绘制、离线地图管理、实测优化这个顺序讲每个环节都会给到具体可运行的代码结构和关键代码片段你照着搬就能跑通第一版。2. 环境准备百度地图开发者认证与工程配置的三座大山每次写百度地图相关的文章总有人在评论区问“为什么我跑起来黑屏”“为什么key报错”。这部分我不打算一句带过因为这一关过不去后面全是白搭。2.1 开发者认证与AK申请百度地图开放平台现在要求开发者完成个人或企业认证后才能创建应用获取AKAccess Key认证本身是免费的。进入控制台后创建应用时选择“Android SDK”系统会让你填包名和SHA1签名指纹。这里有个非常容易翻车的细节你测试用debug签名、上线用release签名两者的SHA1完全不同如果只配置了一个另一个环境必然报鉴权失败。我见过不少项目上线后地图白屏查了半宿最后发现release包没配密钥。获取签名指纹的常规命令keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android如果你用的是Android Studio自动生成的debug签名上面这条命令直接能用。上线包的SHA1从你正式的jks文件里取。2.2 工程配置jar包、SO文件与Manifest缺一不可百度地图SDK当前主流集成方式有两种一种是去官网下载完整SDK包手动拷jar和so另一种是通过Maven仓库远程依赖。2019年之后的版本官方建议用Maven配置方式如下implementation com.baidu.lbsyun:BaiduMapSDK_Map:7.5.3 implementation com.baidu.lbsyun:BaiduMapSDK_Location:9.1.8.1但这里我要多说一句如果你的项目存在复杂的多渠道打包、混淆配置或者对包体积有极端要求手动集成SDK反而更可控。因为百度地图的so库按CPU架构分了arm64-v8a、armeabi-v7a、x86等目录远程依赖默认全部打入包内体积会膨胀不少。手动集成时你可以只保留arm64-v8a和armeabi-v7aAndroid模拟器需要x86的话再单独加。Manifest里必须配置的内容一个都不能少!-- 地图SDK需要的权限 -- uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE / !-- 定位服务百度定位SDK的前台服务 -- service android:namecom.baidu.location.f android:enabledtrue android:exportedfalse android:process:remote / !-- AK密钥必须写在application节点内 -- meta-data android:namecom.baidu.lbsapi.API_KEY android:value你的AK /这里面的.so文件加载很迷如果你遇到UnsatisfiedLinkError首先检查是不是so库目录和jniLibs路径对不上其次检查是不是只拷了jar没拷so。官方SDK文档里写得分散我当年第一次集成时漏了so直接一个崩溃教做人。2.3 官方Demo先跑通再做业务逻辑拿到SDK后我建议任何开发者都先别写业务代码把官方Demo下载下来替换成自己的AK确认地图能显示、定位蓝点能跳再做后续。官方Demo跑不通说明环境有问题这时候排查范围最小Demo通了再开始写业务出问题时至少能确认不是环境锅。这个习惯救过我很多次属于成本最低的避坑手段。3. 实时定位数据采集决定轨迹质量的第一个关卡轨迹好不好看根源在定位数据的质量不在绘制代码。定位点稀疏、噪声大你用再牛的绘制库也画不出顺滑轨迹。这一章我们解决“数据怎么来”的问题。3.1 用百度定位SDK而不是系统原生定位有人会问Android系统不是自带LocationManager吗为什么还要额外接一套定位SDK核心原因有三个坐标体系一致百度地图用的是BD09LL坐标系而手机原生定位返回的是WGS84坐标如果直接用原生定位的坐标画到百度地图上你会发现轨迹偏移了大概几百米。虽然百度SDK提供了坐标转换工具类但每次转换多一层运算、多一些出错可能不如直接用百度定位SDK返回的坐标天然匹配。定位成功率更高百度的定位SDK融合了GPS、基站、Wi-Fi三种信号源在室内、城市峡谷等场景下比原生定位更稳。定位模式可控百度定位SDK可以设置定位模式、扫描间隔、返回坐标类型做轨迹类应用时这些参数直接影响功耗和精度。3.2 初始化定位Client并设定合理参数定位模块的核心代码结构如下public class LocationTracker { private LocationClient locationClient; private BDAbstractLocationListener locationListener; private OnLocationUpdateListener updateListener; public void init(Context context) { locationClient new LocationClient(context); LocationClientOption option new LocationClientOption(); // 高精度模式GPS 网络定位结合 option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); // 返回百度经纬度坐标系这是默认值但要显式确认 option.setCoorType(bd09ll); // 定位频率单位毫秒轨迹类应用建议1000~2000 option.setScanSpan(1000); // 不需要逆地理编码省电也省流量 option.setIsNeedAddress(false); // 设置缓存策略为不缓存保证每个点都是实时计算 option.setLocationCacheEnable(false); locationClient.setLocOption(option); locationListener new BDAbstractLocationListener() { Override public void onReceiveLocation(BDLocation location) { if (location null) return; if (location.getLocType() BDLocation.TypeGpsLocation || location.getLocType() BDLocation.TypeNetWorkLocation || location.getLocType() BDLocation.TypeOffLineLocation) { if (updateListener ! null) { updateListener.onLocationUpdate( new LatLng(location.getLatitude(), location.getLongitude()), location.getSpeed(), location.getLocType() ); } } } }; locationClient.registerLocationListener(locationListener); } public void start() { if (locationClient ! null !locationClient.isStarted()) { locationClient.start(); } } public void stop() { if (locationClient ! null locationClient.isStarted()) { locationClient.stop(); } } }这段代码里有几个参数值得展开说setScanSpan(1000)定位回调频率。轨迹类应用我建议1到2秒一个点兼顾精度和流畅度。你调到100毫秒轨迹是细了但电量哗哗掉而且定位本身存在物理误差太密的点反而放大抖动。setIsNeedAddress(false)如果你不需要显示街道名、POI信息一定关掉。逆地理编码会发起网络请求在线模式下增加流量消耗离线模式下根本返回不了。setLocationCacheEnable(false)这个很多人会忽略。百度定位SDK默认会缓存上一次的定位结果。如果开着冷启动时可能先回调一个旧点你的轨迹起点会“瞬移”一下。3.3 定位回调里的过滤逻辑不写的话轨迹没法看定位是一个带噪声的过程。车停着GPS漂移会让点在一两米范围内跳来跳去人走过高楼底下点直接跳出去几十米甚至上百米。如果你不做任何过滤画出来的轨迹就是一团乱麻。我常用的过滤逻辑分三层无效点过滤latitude和longitude为0或者locType不是有效类型直接丢弃。速度异常过滤如果两点间计算出的平均速度超过某个阈值比如180km/h认为发生了跳点丢弃或修正。距离突变过滤相邻两点距离如果超过当前场景下的合理值比如120km/h * 定位间隔判定为漂移点。速度判断要利用BDLocation自带的getSpeed()方法单位是m/s。如果两次定位间隔内速度正常但距离却突变了说明定位源切换或环境干扰通常做法是跳过该点。我实际用的是距离突变过滤阈值代码大致如下private boolean isReasonablePoint(LatLng current, LatLng previous, long intervalMs) { if (previous null) return true; double distance distanceBetween(previous, current); // 最大速度按120km/h估算换算成ms间隔内的最大合理距离 double maxDistance 33.3 * (intervalMs / 1000.0) * 1.5; return distance maxDistance; }乘以1.5是给加速过程留余量。这个阈值你要根据业务场景调——骑行和公路汽车完全不是一个量级。4. 轨迹绘制核心Polyline渲染与坐标队列管理定位数据有了现在开始解决“怎么画”。这一章同时覆盖在线和离线两种模式因为绘制层逻辑是共通的。4.1 一个List维护轨迹一组Polyline绘制百度地图SDK绘制轨迹最核心的API是BaiduMap.addPolyline()传入PolylineOptions其中核心是坐标点列表。实时轨迹的核心思路是维护一个全局坐标点List每次定位回调到来往List里追加一个点然后把List整体设置给Polyline对象。public class TrackOverlay { private BaiduMap baiduMap; private ListLatLng trackPoints new ArrayList(); private Polyline polyline; public void init(BaiduMap map) { this.baiduMap map; PolylineOptions options new PolylineOptions() .width(8) .color(0xFF3467F0) .visible(true) .dottedLine(false); polyline baiduMap.addPolyline(options); } public void appendPoint(LatLng latLng) { trackPoints.add(latLng); polyline.setPoints(trackPoints); } }这段代码看着简单但有两个性能细节你必须知道setPoints和addPoint的区别网上很多旧代码用polyline.setPoints()传入新list这个没问题但不要频繁调用Map.clear()再重新addPolyline那会导致图层频繁重建掉帧严重。点数量过大时的性能退化当trackPoints超过几千个点绘制会明显变卡。原因在于GPU每帧需要处理的顶点数暴增。解决方案是抽稀——保留关键点丢弃冗余点。比如直线段上的中间点对轨迹形状没有贡献可以用Douglas-Peucker算法或简单的“最小夹角过滤”进行删减。4.2 在线模式与离线模式的绘制差异在线和离线两种模式下appendPoint这个逻辑完全不用变变的只是地图底图的数据来源。在线模式百度地图SDK自动从服务器拉取瓦片你只管往地图上加覆盖物Polyline。这个模式在室内、市区、网络稳定的场景下体验最好瓦片清晰、更新及时。离线模式你需要先下载离线地图包SDK会优先读取本地瓦片数据网络断开时底图依然能显示。覆盖物Polyline不依赖网络因此轨迹绘制不受影响。也就是说代码层面你只需要处理“有没有离线包”绘制逻辑不需要区分。这也是我推荐的方式——不要在绘制层做两套代码统一使用BaiduMap.addPolyline()把差异交给地图引擎去消化。4.3 绘制增量更新不要每次全量刷新很多新手写法是拿到新点后Map.clear()然后重新addPolyline全部点。这种方式在点少时没感觉点多了就致命。正确做法是首次创建Polyline时传入空list或第一个点。后续新点到达在内存list里追加再调用polyline.setPoints()。调用setPoints()时百度地图内部会做diff和局部更新比整条线刷新高效得多。另外如果两点间距特别近小于1米我通常选择不更新轨迹等待下一个有效点再画这样可以防止视觉上出现停滞感实测效果更流畅。4.4 轨迹起点和终点的标记纯跑线会让人看不清从哪出发、在哪结束。建议叠加两个Markerprivate void addStartMarker(LatLng start) { BitmapDescriptor icon BitmapDescriptorFactory .fromAsset(start_marker.png); MarkerOptions options new MarkerOptions() .position(start) .icon(icon) .anchor(0.5f, 0.5f) .zIndex(10); baiduMap.addOverlay(options); }终点Marker会在轨迹停止时添加也可以做成动态更新的样式比如终点跟随最后一点移动。如果不想引图片资源直接用BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)和HUE_RED区分起终点最省事。5. 离线地图包管理与地图生命周期两个最容易翻车的细节这一章讲两个最常见也最隐蔽的问题离线包怎么下、地图引擎的生死怎么管。两个问题处理不好App不崩也得闪退。5.1 离线地图包的下载与切换逻辑百度地图SDK提供OfflineMapManager来管理离线包。核心流程分三步第一步获取城市列表并下载。离线地图按城市维度拆分你不可能把全国都下下来除非你的App内置超大存储。一个通用做法是定位成功后判断当前城市是否已有离线包没有则自动触发下载。OfflineMapManager offlineMapManager new OfflineMapManager(context, listener); // 通过城市名下载对应离线包 ArrayListMKOLUpdateElement allCities offlineMapManager.getAllOfflineMapInfo(); // 或者按城市码下载如何拿到城市码后面说 offlineMapManager.download(北京);第二步监听下载进度和状态。下载是异步的必须监听回调来更新UI。官方给的监听器接口比较繁琐我封装一层简化一下offlineMapManager.setOnDownloadStateListener(new OfflineMapManager.OfflineMapDownloadListener() { Override public void onDownloadUpdate(int index, int cityID, float ratio) { // ratio 表示当前城市的下载进度 0~1 } Override public void onDownloadFinish(int index, int cityID) { // 下载完成此时切换离线模式 } Override public void onDownloadFailed(int index, int cityID, int errno) { // 下载失败常见原因网络断开、空间不足、重复下载 } });这里有个关键点下载完成的回调并不等于离线包立刻可用。你最好在下一次启动地图时把离线包目录检查一遍确认文件存在。有人在这里遇到“明明下载了离线包离线模式却没生效”的问题大概率是下载完成回调触发了但文件还没flush到磁盘App就直接被杀导致数据缺失。第三步开启离线模式。百度地图SDK默认是“在线优先离线兜底”。实际上当你断网时引擎自动尝试本地瓦片不需要手动调开关。你唯一需要做的是确认离线包下载完整、存放在默认路径下。如果你的应用需要用户主动选择“仅使用离线地图”可以在设置页存一个flag然后在地图加载时通过MapView.setMapCustomEnable(false)或控制BaiduMapOptions的相应属性来限制网络请求。严格说官方SDK没有提供一个公开、稳定的接口能完全禁止在线瓦片加载所以实际项目中多数人采用的做法是断网场景下仍然允许SDK自己切换只确保离线包存在即可。5.2 地图生命周期管理悬在一根弦上百度地图的MapView是一个重量级View内部持有地图引擎生命周期必须严格绑定Activity或Fragment的生命周期。这一步漏了轻则内存泄漏重则崩溃。官方要求的标准写法Override protected void onResume() { super.onResume(); mapView.onResume(); } Override protected void onPause() { super.onPause(); mapView.onPause(); } Override protected void onDestroy() { // 先清覆盖物再销毁地图 baiduMap.clear(); mapView.onDestroy(); mapView null; super.onDestroy(); }别小看这个模板我见过至少三个项目因为漏了onResume/onPause导致地图黑屏或卡死。尤其是Fragment场景如果你在onDestroyView里没有把MapView从父容器移除并置空再次进入页面时引擎状态会混乱。还要注意baiduMap.clear()会清除地图上所有的覆盖物包括你自己添加的Marker和Polyline。所以清完后需要重新添加覆盖物或者你可以在保存轨迹数据后恢复绘制。这里我通常的做法是clear前备份所有点恢复时重放一遍。这个开销很小但能避免很多奇怪问题。6. 轨迹漂移处理与多机型兼容实测轨迹画出来不代表体验就合格。我在真机测试时踩过的坑比写代码时踩的还多用一个章节集中讲透。6.1 漂移点不处理轨迹直接乱飞我的实测场景之一是骑电动车绕小区一圈。华为某机型在树荫下定位点会每隔几秒偏出去十几米。轨迹画出来就成了锯齿状甚至穿楼而过。这里我采用的方案是卡尔曼滤波太复杂我用“速度距离”双重过滤再加一个简单的角度平滑。角度平滑的思路如果三个连续点形成的夹角接近180度比如大于165度说明轨迹在朝反方向折返这种跳变大概率是漂移。此时将第三个点丢弃等待下一个点。伪代码逻辑if (pointList.size() 2) { LatLng last pointList.get(pointList.size() - 1); LatLng secondLast pointList.get(pointList.size() - 2); double angle calculateAngle(secondLast, last, newPoint); if (angle 165.0) { // 疑似剧烈折返丢弃新点 return; } } pointList.add(newPoint);这个方案对低速骑行场景效果不错但对高速移动场景比如开车上高速就不适合了因为高速变道本身就是小角度。你要根据业务场景去调阈值。6.2 不同手机的“体质差异”很大热搜关键词里有一条“不同手机的百度地图定位功能测试”这个确实是绕不开的现实。我手头测试过的小米、华为、OPPO、三星同一套代码、同一个版本表现差异非常大小米系列后台定位容易被系统清理需要引导用户开启“自启动”和“省电策略-无限制”。LocationClient.start()后如果长时间收不到回调大概率是被系统限制了。华为系列高精度模式下GPS信号弱时网络定位补位快但卫星定位和网络定位返回的坐标偶尔有断层。需要在回调里根据getLocType()判断定位源设置不同的置信度。三星系列有个老bug定位权限授予后需要重启应用才能生效。如果用户首次安装后点击“仅使用时允许”后续切换成“始终允许”进程需重启。我的建议是不要试图用一套定位参数适配所有机型而是在设置页提供“定位模式”选项高精度/省电/仅设备让用户根据场景自选。同时在定位失败或长时间无回调时给出明确提示而不是让界面看起来像死了一样。6.3 对轨迹上传和回放的一点扩展思考百度的定位和地图SDK解决的是“采集显示”但真正把轨迹做成产品的还需要数据链路把轨迹点批量上传到服务端历史回放时再拉下来重新绘制。我做这类功能时有个经验上传数据不传完整点列表先做抽稀再上传能省70%以上的流量。判断抽稀点是否该保留可以用“该点到前一段直线的距离是否大于阈值”来决定——比如偏离超过3米的点保留小于的直接丢弃。直线路段上几十个点最终只剩两端效果和原轨迹肉眼无差但服务端存储和客户端渲染压力骤降。另外经纬度坐标在传输时建议用整数形式放大1e6倍丢给服务端用整型存储既省空间又避免浮点字符串解析误差。7. 后记我用这套方案跑通一次真实户外测试最后分享一次完整的实测记录算是给这套方案做一个真实性背书。测试设备小米13Android 14CPU是arm64架构。场景从杭州滨江骑车到西湖全程约15公里一半在城市道路在线模式一半在南山路林荫区中间断网模拟离线。定位间隔1秒采用高精度模式。结果如下在线模式段轨迹平滑度良好Polyline无卡顿定位点间距实际在5~15米之间。断网段我把数据流量关闭底图切到已下载的杭州离线包轨迹依然实时刷新没有任何断层感。全程约3000坐标点绘制性能稳定全程没有调用Map.clear()仅在起点和终点添加了Marker。电量消耗约每小时12%在可接受范围内。要说还有什么遗憾离线包版本如果和在线瓦片版本不一致过境时会看到两块底图的新旧差异在急需精准底图的场景会有些违和。解决办法是定期提醒用户更新离线包SDK自身也提供了增量更新机制自动检测并更新。如果你正打算做运动轨迹记录、车辆监控、巡检打卡这类应用这个方案可以直接拿去当第一版技术底座。先跑通在线绘制再逐步加离线包、轨迹抽稀、后台保障和上传链路。每加一层自己亲手实测一轮踩过的坑将来都会变成你兜里的经验。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。