1. 为什么需要GPS定位模拟技术最近在开发一款骑行类App时遇到了一个很有意思的需求需要在演示时模拟用户骑行的轨迹动态展示。简单来说就是让地图上的小蓝点按照预设路线移动就像真的有人在骑行一样。这种技术在智能硬件开发、运动类App演示、导航系统测试等场景中都非常实用。我最初尝试用真实设备进行测试发现几个痛点首先要完整测试一条骑行路线得真的骑上几公里其次重复测试相同路线时每次骑行轨迹都会有细微差异最重要的是开发阶段频繁外出测试实在太费时间。这时候GPS定位模拟技术就成了救命稻草。提示Android系统从4.0开始就提供了完善的模拟定位API但很多开发者不知道如何正确使用。2. 搭建基础开发环境2.1 创建Android项目首先新建一个Android Studio项目最低API级别建议设为21Android 5.0。这个版本对定位API的支持已经比较完善而且市面上绝大多数设备都兼容。在build.gradle中添加百度地图SDK依赖implementation com.baidu.mapapi:map-sdk:7.5.0 implementation com.baidu.mapapi:search-sdk:7.5.0记得在AndroidManifest.xml中添加必要权限uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION/ uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION/ uses-permission android:nameandroid.permission.ACCESS_MOCK_LOCATION/2.2 初始化百度地图在Application类中初始化百度地图SDKpublic class MyApp extends Application { Override public void onCreate() { super.onCreate(); SDKInitializer.setAgreePrivacy(this, true); SDKInitializer.initialize(this); } }地图Activity的基本布局com.baidu.mapapi.map.MapView android:idid/mapView android:layout_widthmatch_parent android:layout_heightmatch_parent/3. 实现GPS定位模拟3.1 获取LocationManager实例定位模拟的核心是Android的LocationManager类。首先获取其实例LocationManager locationManager (LocationManager) getSystemService(Context.LOCATION_SERVICE);3.2 设置模拟定位提供者Android支持多种定位提供者GPS、网络等我们需要为每种提供者设置模拟数据// 需要模拟的定位提供者列表 String[] mockProviders {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER}; for (String provider : mockProviders) { // 如果提供者不存在先添加测试提供者 if (!locationManager.getAllProviders().contains(provider)) { locationManager.addTestProvider( provider, false, // requiresNetwork false, // requiresSatellite false, // requiresCell false, // hasMonetaryCost true, // supportsAltitude true, // supportsSpeed true, // supportsBearing android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE ); } // 启用测试提供者 locationManager.setTestProviderEnabled(provider, true); }3.3 发送模拟位置数据这是最关键的步骤我们需要构造Location对象并发送给系统Location mockLocation new Location(provider); mockLocation.setLatitude(39.915); // 纬度 mockLocation.setLongitude(116.404); // 经度 mockLocation.setAccuracy(5); // 精度(米) mockLocation.setSpeed(5.5f); // 速度(米/秒) mockLocation.setBearing(90f); // 方向角 mockLocation.setTime(System.currentTimeMillis()); if (Build.VERSION.SDK_INT Build.VERSION_CODES.JELLY_BEAN_MR1) { mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); } locationManager.setTestProviderLocation(provider, mockLocation);4. 获取骑行路线坐标数据4.1 使用高德地图API获取路径虽然我们最终要在百度地图上展示但高德地图的路径规划API更易用。可以通过高德地图的Web服务API获取两点间的骑行路径坐标访问高德地图开放平台申请Web服务API Key调用路径规划接口示例请求URLhttps://restapi.amap.com/v4/direction/bicycling?origin116.434307,39.90909destination116.434446,39.90816key你的KEY返回的JSON数据中会包含完整的路径坐标点GCJ-02坐标系。4.2 坐标批量转换工具由于高德使用GCJ-02坐标系而百度使用BD-09坐标系需要进行转换。推荐使用在线工具批量转换打开坐标转换工具网站粘贴GCJ-02坐标选择GCJ02转BD09获取转换后的坐标注意批量转换时要确保坐标格式正确通常是经度,纬度成对出现每对坐标占一行。5. 解决百度地图坐标偏差问题5.1 坐标系转换算法如果不想依赖在线工具可以使用以下Java方法进行坐标转换private static final double x_pi 3.14159265358979324 * 3000.0 / 180.0; public static double[] gcj02ToBd09(double[] gcj) { double x gcj[0], y gcj[1]; double z Math.sqrt(x * x y * y) 0.00002 * Math.sin(y * x_pi); double theta Math.atan2(y, x) 0.000003 * Math.cos(x * x_pi); double[] bd new double[2]; bd[0] z * Math.cos(theta) 0.0065; bd[1] z * Math.sin(theta) 0.006; return bd; }5.2 处理网页版与手机版坐标偏差百度地图网页版和手机版存在固定偏差这是很多开发者容易忽略的问题。解决方法是在转换后的坐标上再加一个固定偏移量// 网页版百度坐标转手机版 public static double[] webBdToMobileBd(double[] webBd) { double[] mobileBd new double[2]; mobileBd[0] webBd[0] 0.0060; mobileBd[1] webBd[1] 0.0065; return mobileBd; }6. 实现动态轨迹展示6.1 平滑移动算法要让标记点平滑移动而不是跳跃式移动需要实现插值算法private ListLatLng generateSmoothPath(LatLng start, LatLng end, int points) { ListLatLng path new ArrayList(); double latStep (end.latitude - start.latitude) / points; double lngStep (end.longitude - start.longitude) / points; for (int i 0; i points; i) { double lat start.latitude latStep * i; double lng start.longitude lngStep * i; path.add(new LatLng(lat, lng)); } return path; }6.2 使用ValueAnimator实现动画Android的ValueAnimator非常适合用来实现平滑移动效果ValueAnimator animator ValueAnimator.ofInt(0, path.size() - 1); animator.setDuration(5000); // 5秒完成整段路径 animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(animation - { int index (int) animation.getAnimatedValue(); LatLng current path.get(index); // 更新标记点位置 marker.setPosition(current); // 可选移动地图视角跟随标记 MapStatusUpdate update MapStatusUpdateFactory.newLatLng(current); baiduMap.animateMapStatus(update); }); animator.start();7. 常见问题与解决方案7.1 模拟定位不生效可能原因及解决方法未开启允许模拟位置在开发者选项中开启权限不足确保有ACCESS_MOCK_LOCATION权限测试提供者未正确设置检查addTestProvider参数其他应用占用了定位服务关闭其他可能使用定位的App7.2 轨迹显示不连贯优化建议增加路径点的密度调整动画持续时间使用更平滑的插值算法如贝塞尔曲线考虑设备性能适当降低帧率7.3 百度地图标记跳动典型原因坐标系转换不准确网页版和手机版坐标混淆精度损失导致的位置偏移检查步骤确认使用正确的转换算法检查是否应用了手机版偏移量对比实际坐标与显示坐标的差异在实际项目中我发现最稳妥的做法是先用百度地图的坐标拾取工具获取几个关键点的坐标然后与程序中的坐标进行对比找出固定偏差模式。这个方法虽然看起来有点笨但能有效解决大多数偏移问题。