新闻详情

新闻详情

首页 / 资讯中心 / 详情

安卓跑步App开发实战:高德地图轨迹绘制与定位踩坑全解析

发布时间:2026/9/26 21:22:52来源:尧图网络
安卓跑步App开发实战:高德地图轨迹绘制与定位踩坑全解析
简介一个基于安卓开发工具的跑步运动应用完整工程源码面向安卓学习者、移动应用开发爱好者以及需要完成课程设计或毕业设计的计算机专业学生。项目采用Java语言编写实现实时记录跑步速度、动态绘制定位运动路径、管理历史跑步履历并查看详情等功能覆盖定位服务、轨迹绘制、数据持久化等常见移动开发场景。压缩包共一百二十九个文件包含二十二个源码文件、三十二个界面布局与配置资源、地图相关软件开发工具包及动态库、构建脚本等整体包体大小约十八兆目录结构清晰可直接用安卓开发工具打开编译运行。目前已有两千五百四十人学习下载说明该资源具备较好的参考价值。通过阅读代码可以掌握地图开发工具包的集成方式、路径轨迹绘制逻辑以及跑步数据的管理思路对提升安卓实际开发能力很有帮助。1. 一个能跑能画轨迹的安卓跑步App先从能复现的源码包说起我接过一个跑步类的安卓项目第一反应是这东西很简单——不就是GPS定位加画线吗等到自己动手才发现坑全埋在定位回调的时序、坐标系的偏移和轨迹点的密度上。这个源码包是一个基于Android Studio Java 的安卓跑步App核心是高德地图3D SDK 6.8.0能实时记录速度、在地图上画出跑步路径并把每次跑步的数据存成履历点开还能看详细轨迹。对正在做运动类App、或者想抄一套地图轨迹方案的开发者来说它比碎片化的博客答案完整得多。下面从工程结构拆起一路讲到定位、画线、存数据和踩坑记录。2. 工程的骨头和肉从Gradle配置到高德地图SDK的接入2.1 高德地图SDK 6.8.0为什么够用选型与限制跑步App要展示动态轨迹用高德的3D地图SDK比2D更合适3D版支持Polyline绘制、地图旋转倾斜和实时定位叠加视觉上更接近主流运动软件。这个包里带的是Android_Map3D_SDK_V6.8.0_20190401.jar是2019年的版本虽然不算新但基础地图、定位、轨迹绘制这些API都非常稳定拿来做demo或二次开发完全够。选型时要注意它的限制第一高德地图SDK的坐标是GCJ-02加偏后的坐标不能和原始GPS坐标混用第二这个jar包依赖完整的高德地图服务没有网络就是空地图第三3D地图包比2D包大不少安装包体积会上去。所以如果你的App只跑室内或固定场地这条路不一定最合适但做户外跑步记录这套组合是成熟方案。2.2 导入工程后先改包的入口build.gradle和AndroidManifest拿到源码包后不要急着开跑先把Gradle配置捋一遍。项目里的build.gradle是App模块的构建文件要看三段compileSdkVersion、defaultConfig、dependencies。高德6.8.0的jar放在libs目录里用文件形式引入不参与远程依赖。android { compileSdkVersion 30 defaultConfig { applicationId com.runner.app minSdkVersion 21 targetSdkVersion 30 } } dependencies { implementation files(libs/Android_Map3D_SDK_V6.8.0_20190401.jar) }这段配置决定了App的最低兼容版本和编译版本。minSdkVersion 21表示Android 5.0以上都能跑targetSdkVersion 30意味着你必须在运行时动态申请定位权限不能在Manifest里声明完就完事。高德SDK里的绘制能力依赖Activity context和地图生命周期绑定如果minSdk低于19部分地图API不会生效。接着看AndroidManifest.xml权限是关键。缺少定位权限地图能显示但轨迹永远是一条直线缺少网络权限地图直接白屏。至少要有这些uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.WAKE_LOCK /ACCESS_FINE_LOCATION是GPS精确定位的前提ACCESS_COARSE_LOCATION用来在弱网环境下做辅助定位WAKE_LOCK保证息屏后CPU和GPS还能继续工作。很多人只加了前两个权限结果App一锁屏轨迹就断后来才发现是缺了WAKE_LOCK。另外6.0以上系统运行时权限也要写因为Manifest里的权限只保证安装时声明不保证运行时授给你。2.3 gradlew.bat和gradle-wrapper.jar的作用别在构建脚本里乱加版本源码包里有gradlew.bat、gradlew和gradle-wrapper.jar这三个文件把Gradle版本锁死在项目本地保证别人拉下来用同一个版本构建。很多新手图省事自己装了个新Gradle然后直接改build.gradle里的版本号往往半天编不过。cd 项目目录 ./gradlew assembleDebug第一次构建会通过gradle-wrapper.jar下载对应的Gradle版本这个过程需要网络。如果你在Android Studio里直接打开项目IDE会自动读gradle-wrapper.properties里的版本号不需要手动执行这条命令。我一般会检查一下gradle-wrapper.properties确保distributionUrl指向的Gradle版本和网络能访问的仓库匹配。这里最容易翻车的是把gradle版本升级到7.x但项目里还有老依赖没有兼容迁移成本全花在修构建报错上。这个demo用的是老一套能用就别乱升。项目里还有.gitignore和settings.gradle这些不用动。.gitignore已经帮你把local.properties和build/目录排除了方便直接提交到自己的Git仓库。3. 实时记录速度和绘制路径LocationListener里的经纬度怎么变成地图上的线3.1 定位回调拿到经纬度之后的第一个判断跑步App的核心在mapActivity.java。先看定位回调用的是高德的AMapLocationListeneronLocationChanged会高频回调频率默认是2秒一次也可以手动调。回调里拿到的AMapLocation不是每次都有用我习惯先做三道检查Override public void onLocationChanged(AMapLocation location) { if (location null || location.getErrorCode() ! 0) { return; } double lat location.getLatitude(); double lng location.getLongitude(); if (lastLat 0 lastLng 0) { lastLat lat; lastLng lng; lastTime System.currentTimeMillis(); return; } float[] results new float[1]; Location.distanceBetween(lastLat, lastLng, lat, lng, results); float distance results[0]; if (distance 5) { return; } totalDistance distance; latLngList.add(new LatLng(lat, lng)); lastLat lat; lastLng lng; }第一道检查是getErrorCode() ! 0这个错误码很关键GPS没锁、网络定位失败的时候它返回的值不是0这时候拿到的坐标是脏值。第二道检查是第一个点不做距离计算因为前一秒的基准点还没建立。第三道检查是距离阈值判断我设了5米小于这个距离直接丢弃因为人在原地跑、晃手机的抖动会让轨迹画成一团毛线。这段代码的时序是先过滤脏数据再算位移最后才把点加进坐标数组。3.2 距离和速度计算直接调用getSpeed()为什么不可靠高德定位SDK的AMapLocation里有getSpeed()方法但这个速度来自GPS芯片在室内启动、刚走出隧道或信号不稳时经常返回0或者突然跳到几十米每秒。做过运动App的都知道这是个黑匣子不能直接拿给用户看。更稳的做法是自己算用两点间的距离除以两个定位点的时间差算出的平均速度才是这段时间的真实速度。long now System.currentTimeMillis(); double intervalSec (now - lastTime) / 1000.0; double speed distance / intervalSec; // 单位米/秒 lastTime now; if (speed maxSpeed) { maxSpeed speed; }注意distance的单位是米intervalSec是秒算出来的速度是m/s跑步App里一般要转成配速用min/km展示配速 总时间分钟 / 总距离公里。这个逻辑放在UI层做别混在数据计算里。另外要处理intervalSec为0的场景GPS两次回调可能间隔不到1毫秒直接除会得到inf所以你在代码里最好加个判断intervalSec 0.1时就跳过本次计算。这个自己算的speed还有个好处能判断用户是不是在剧烈晃动。比如GPS漂移导致位移5米但只花了0.1秒算出来的速度高达180km/h明显是脏数据应该过滤掉。常见的做法是加一个if (speed 10)上限超过10m/s36km/h就认为当前定位异常不计入总距离。跑步场景下很难超过4m/s10m/s这个阈值足够宽松又足够安全。3.3 用Polyline把坐标画上地图并让摄像头跟着跑轨迹线用高德的Polyline画。初版实现最容易犯的错误是每次定位都调用aMap.addPolyline()重新画一条线这样跑十分钟就是几百条线叠在一起卡成PPT。正确做法是只创建一次Polyline对象之后用setPoints()更新所有坐标点。Polyline polyline null; if (polyline null) { PolylineOptions options new PolylineOptions() .addAll(latLngList) .width(12) .color(Color.RED); polyline aMap.addPolyline(options); } else { polyline.setPoints(latLngList); }代码逻辑很直白第一次定位到的坐标放进latLngList创建Polyline并画出来后续每次都把整个列表重新塞给同一个Polyline对象。高德的setPoints内部会重新渲染但不会重复创建地图对象性能开销小很多。需要注意的是latLngList是ArrayList你往里加点的操作只发生在主线程否则会并发修改崩溃。摄像头跟随也在这个方法里处理。我的做法是让地图平滑移动到最新点但不要每次回调都强制重新定位视角否则手机会被晃晕if (isFirstFix) { aMap.animateCamera(CameraUpdateFactory.newLatLngZoom( new LatLng(lat, lng), 18f)); isFirstFix false; }这个isFirstFix标志只在地图首次定位到用户时置为true之后就不再自动拉动视角。如果你做成跑步软件那种“始终跟随”可以把animateCamera放在一个定时器里每5秒触发一次这样既保持跟随感又不会每秒都打断地图交互。4. 跑步数据履历与详情SQLite存轨迹RecyclerView列记录点击回看4.1 建表把轨迹点序列存成逗号分隔还是独立表跑步结束以后要把距离、时长、平均速度和轨迹点保存下来。这个demo用的是SQLite没有引入Room因为数据量不大手写SQLite反而更直观。表结构我建议只建一张表把轨迹点序列化成一个字符串跑一次5公里大约产生300到500个点单个字符串也就十几KB完全扛得住。CREATE TABLE run_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, start_time LONG, duration LONG, distance REAL, avg_speed REAL, max_speed REAL, polyline TEXT );start_time存的是跑步开始的时间戳用来排列表顺序duration存总时长单位秒distance存总距离单位米polyline存经纬度序列我用分号和逗号组合格式比如31.2304,121.4737;31.2305,121.4738。为什么不用独立表存点因为每次查询详情都要关联两张表履历列表还要做JOIN性能反而差。点序列存在一个字段里详情页直接解析字符串就能重绘轨迹。插入数据的代码长这样SQLiteDatabase db helper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(start_time, startTime); values.put(duration, duration / 1000); values.put(distance, totalDistance); values.put(avg_speed, totalDistance / (duration / 1000.0)); values.put(max_speed, maxSpeed); values.put(polyline, latLngToString(latLngList)); db.insert(run_record, null, values); db.close();注意duration要除以1000转成秒再存如果不转后面算配速时会多乘一个1000数字大得离谱。latLngToString是我自己写的一个工具方法用StringBuilder循环拼接最后去掉末尾分号。这里有个小坑SQLite的TEXT字段没有长度限制但如果你一次性拼接几千个点字符串会有几十KB插入时没问题查询时要注意内存。跑步5公里通常不会超过500个点放心用。4.2 履历列表RecyclerView展示最近N条记录履历页面用RecyclerView加载历史记录列表项显示日期、距离、配速、时长。数据查询很简单按开始时间倒序取20条String sql SELECT * FROM run_record ORDER BY start_time DESC LIMIT 20; Cursor cursor db.rawQuery(sql, null);LIMIT 20是给你自己留个缓冲跑步App跑久了会有几百条记录全部加载进内存不仅慢RecyclerView滑动也会卡。如果用户要看更早的记录再分批加载。这个demo里没有做分页加载但你在二次开发时应该优先考虑。适配器里要把distance转成公里小数把duration转成mm:ss格式。注意数据库存的单位是米和秒展示时除以1000保留一位或两位小数。配速的算法是配速 duration / (distance / 1000.0)得到的是每公里秒数再转成530这种格式。很多跑步用户的习惯是看配速而不是看平均速度所以你要同时算这两个值。点击某条记录跳转到详情页面携带记录的id。详情页再根据id重新查询一次单条记录把polyline字段解析出来画图。4.3 详情页加载轨迹点并重画那条路线详情页的核心是解析polyline字符串。别用split(;)两次效率不高但简单进阶一点可以用StringTokenizer。我给出一个最直接的解析方法public static ArrayListLatLng parsePolyline(String polyline) { ArrayListLatLng points new ArrayList(); if (polyline null || polyline.isEmpty()) return points; String[] pairs polyline.split(;); for (String pair : pairs) { String[] latLng pair.split(,); if (latLng.length 2) { double lat Double.parseDouble(latLng[0]); double lng Double.parseDouble(latLng[1]); points.add(new LatLng(lat, lng)); } } return points; }解析后直接扔给Polyline重绘逻辑和跑步中的绘制一样只需要一次aMap.addPolyline()。如果有同一条线路被跑过多次你可以用不同颜色区分高德Polyline支持按线段分段设置颜色但demo里没做。详情页还可以展示平均配速、最大速度、总爬升海拔这些数据源需要在跑步过程中额外记录。这里有个体验上的细节详情页的地图加载完成后调用aMap.moveCamera()把视野调整到能够完整显示整条轨迹的范围。做法是先遍历所有点算出经纬度最大最小值然后构造一个LatLngBounds最后aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50))。否则地图停在默认位置用户看到一条莫名其妙的曲线得手动拖动才能看清。5. 避坑指南高德地图黑匣子和安卓权限那些翻车现场5.1 高德key校验失败地图白屏现象地图区域一片空白没有网格控制台打出INVALID_USER_KEY或AMAP_KEY_AUTH_ERROR。原因高德地图SDK在初始化时会用当前应用的包名和SHA1签名去校验服务端配置的key。如果你用了自己的签名文件或者Android Studio默认的debug签名和高德控制台里配的不一致校验就失败。很多人是打包后安装到手机上但key用的是debug的SHA1release的包自然白屏。解决去高德开放平台控制台找到你的应用分别配置debug和release的SHA1包名。获取SHA1的标准做法keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android然后把打印出来的SHA1填进控制台重新生成key并替换AndroidManifest.xml里的meta-data。我一般会把debug和release都配上避免后面打包又白屏一次。注意控制台改配置后有几秒钟到几分钟的生效延迟刚改完别急着骂SDK。5.2 后台几分钟就掉GPS轨迹画出来是直线现象手机锁屏或者切到别的App再回来轨迹上出现一段长直线跨越了锁屏期间的整个时间段。原因Android系统在后台会限制App的定位频率和CPU活动。没有前台服务时定位服务被系统挂起GPS不再回调但地图UI还在等下次定位唤醒时会直接跳到新坐标两个点之间被连线看起来就是一条横穿马路的直线。解决跑步App必须用前台服务声明FOREGROUND_SERVICE权限在服务启动时发一个常驻通知。同时在高德定位里把定位模式设置为AMapLocationClientOption.AMapLocationMode.Hight_Accuracy并设置setInterval(2000)和setOnceLocation(false)。前台服务的核心代码Notification notification new Notification.Builder(this, run_channel) .setContentTitle(正在记录跑步) .setContentText(GPS定位中) .setSmallIcon(R.drawable.ic_run) .build(); startForeground(1, notification);Android 8.0以上要建NotificationChannel否则前台服务通知不显示。这一步不做后台跑几分钟就被系统杀掉轨迹自然断掉。我见过太多人以为定位权限都有了就没问题其实前台服务才是保命的。5.3 坐标系偏移GPS经纬度和高德地图对不上现象轨迹画的路线和地图上的道路有明显偏移偏差几十米尤其在桥下、商业区更严重。原因高德地图用的是GCJ-02坐标系而手机原生的GPS输出是WGS-84坐标系两者不是一套基准。直接用原生GPS坐标点上高德地图整体向东南方向偏移。解决不要用android.location.Location拿坐标直接用高德定位SDK的AMapLocation它返回的坐标已经做过偏移转换和高德地图是匹配的。如果业务中不得不接收其他设备的WGS-84坐标用高德提供的CoordinateConverter转换CoordinateConverter converter new CoordinateConverter(); converter.from(CoordinateConverter.CoordType.GPS); converter.coord(new LatLng(lat, lng)); LatLng desLatLng converter.convert();注意CoordType.GPS指的是WGS-84高德的CoordType还有百度、搜狗等别选错。这个坑最容易骗过新手因为地图也能显示轨迹也不断就是路不对等发现已经是跑完一次全程了。5.4 Activity销毁后定位回调还在跑内存泄漏现象退出跑步界面后Logcat还在持续打印定位数据重新进入界面时地图卡顿。原因mapActivity创建了AMapLocationClient但onDestroy时只解绑了地图没有停止定位监听。定位服务持有Activity引用碰到屏幕旋转或返回键Activity销毁不完全内存泄漏。解决在onDestroy中强制释放Override protected void onDestroy() { super.onDestroy(); if (locationClient ! null) { locationClient.unRegisterLocationListener(listener); locationClient.stopLocation(); locationClient.onDestroy(); } if (aMap ! null) { aMap.clear(); } }stopLocation是停止连续定位onDestroy是释放定位资源。这个顺序不能乱先注销监听再停止最后销毁。我自己曾漏掉unRegisterLocationListener结果重新进页面时回调被触发了两次速度和距离全部重复累加最后跑完一看总里程是实际的三倍。6. 进阶轨迹抽稀和省电让App能跑完一场全马6.1 轨迹抽稀点太多地图会卡一小时的跑步会产生上千个坐标点高德地图的Polyline一次性渲染上千个点没问题但加上地图旋转和缩放低端机就会掉帧。我习惯在跑步结束后做一次抽稀用Douglas-Peucker算法把保留在误差阈值内的点去掉。简单写法是每次记录时如果前后三点夹角太小且距直线距离小于2米就丢弃中间点。判断函数如下public static boolean shouldKeep(Location prev, Location curr, Location next) { double ab prev.distanceTo(curr); double bc curr.distanceTo(next); double ac prev.distanceTo(next); double delta (ab bc) - ac; return delta 2.0; // 偏差大于2米保留 }这里的delta表示折线比直线多出来的长度值越小说明弯曲越小。保存到SQLite之前先跑一遍轨迹点能减少30%到40%地图流畅度明显提升。6.2 定位参数调优省电与精度的平衡定位间隔是电量消耗的大头高德默认2秒一次对跑步来说够用但如果你想跑马拉松连续四五个小时高频定位会显著发热。我常用的参数组合场景定位间隔单次定位传感器室内预热10秒是网络优先户外跑步2秒否GPS优先完赛徒步5秒否平衡模式代码里用AMapLocationClientOption设置setInterval(2000)只对连续定位生效。如果切换到徒步把间隔改成5000节省约两成功耗。另外把setLocationCacheEnable(false)打开避免缓存坐标导致轨迹跳变。6.3 导出GPX给轨迹留个后悔药跑步App的履历只能自己看但用户总想把轨迹发给别人。我加了个导出GPX的功能把记录里的坐标点直接转成标准XML几十行代码的事StringBuilder gpx new StringBuilder(); gpx.append(?xml version\1.0\ encoding\UTF-8\?\n); gpx.append(gpx version\1.1\ creator\RunnerApp\\n); gpx.append(trknameRun ).append(startTime).append(/nametrkseg\n); for (LatLng point : latLngList) { gpx.append(trkpt lat\).append(point.latitude) .append(\ lon\).append(point.longitude).append(\/trkpt\n); } gpx.append(/trkseg/trk/gpx);导出后保存到getExternalFilesDir很多第三方跑步软件都能读GPX。从那以后我每次做完运动类项目都会强制走一遍“定位断线、坐标系偏移、后台杀进程”这三个检查点确认无误再交付。这个坑多的领域一次完整的踩坑记录比十篇教程都值钱希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

OneCLI密钥注入原理揭秘:为什么AI Agent能调用API却永远碰不到你的API Key 2026/9/26 22:09:56

OneCLI密钥注入原理揭秘:为什么AI Agent能调用API却永远碰不到你的API Key

OneCLI密钥注入原理揭秘:为什么AI Agent能调用API却永远碰不到你的API Key 【免费下载链接】onecli Open-source sandboxed agent harness for teams. Giving every employee a secured personal agent. 项目地址: https://gitcode.com/gh_mirrors/on/onecli …

阅读更多 →
Python实现深度学习基础分析 2026/9/26 22:09:56

Python实现深度学习基础分析

深度学习(Deep Learning)作为人工智能和机器学习的一个重要分支,已经在诸多领域如图像识别、语音处理、自然语言处理等方面取得了突破性进展。它的核心思想是通过模拟人脑神经网络的结构,利用大规模的数据进行训练,帮助计算机自动提取数据中的特征并进行预测。理解深度学习…

阅读更多 →
新手入门公司网站设计:搞定这3点,流量翻倍 2026/9/26 22:09:56

新手入门公司网站设计:搞定这3点,流量翻倍

新手入门公司网站设计:搞定这3点,流量翻倍 网站做好了没人访问?别急着怪算法,多半是设计时就把路堵死了。 很多新手入门做公司网站设计,总以为页面好看、动画炫酷就是赢。结果上线三个月,后台看着想哭:UV(独立访客)只有个位数,转化率接近零。…

阅读更多 →
sem支持32种编程语言完整清单:从TypeScript到SQL的代码diff利器 2026/9/26 22:09:56

sem支持32种编程语言完整清单:从TypeScript到SQL的代码diff利器

sem支持32种编程语言完整清单:从TypeScript到SQL的代码diff利器 【免费下载链接】sem Semantic version control > entity-level diffs, blame, and impact analysis on top of git. 28 languages via tree-sitter. Built for coding agents. 项目地址: https:…

阅读更多 →
DeepOpen Laya 第二轮训练方法深度解析:SupCon 成组采样、R-Drop 正则化与权重/概率集成的完整实验设计 2026/9/26 22:09:56

DeepOpen Laya 第二轮训练方法深度解析:SupCon 成组采样、R-Drop 正则化与权重/概率集成的完整实验设计

【免费下载链接】deepopen 非自回归System 1决策引擎,专为结构化类型决策场景设计 DeepOpen Multilingual, non-autoregressive System 1 decision engine. 项目地址: https://gitcode.com/gh_mirrors/de/deepopen 点击查看 免费下载 导读 本文以 ROU…

阅读更多 →
Sketch智能占位符引擎:JSON Schema驱动的假数据生成方案 2026/9/26 22:09:44

Sketch智能占位符引擎:JSON Schema驱动的假数据生成方案

简介:sketch-data-faker 是一款面向UI/UX设计师与前端开发者的Sketch智能占位符插件,专为提升原型设计效率而生。它基于 Faker.js 等数据源,提供130余种可预测的随机内容类型(如姓名、邮箱、电话、段落、地址等)&#…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉