如何在GoogleMap上实时绘制可重复记录的行走轨迹Polyline
问题根因
实时轨迹绘制失败是三个核心逻辑问题导致的:
- 缺少轨迹存储和绘制对象:没有定义列表保存历史轨迹点,也没有初始化Polyline实例,拿到新坐标后没有载体可以绘制路径
- 定位回调逻辑混乱:
askforPermission方法里注册完定位回调立刻就执行了移除操作,等于注册无效;注册回调时没有指定主线程Looper,回调默认在子线程执行,直接操作地图不会生效 - 状态控制有漏洞:停止记录后
onStart生命周期会自动重新注册定位回调,导致记录状态错乱;开启新记录时只清空了地图内容,没有清空历史轨迹点,旧轨迹会被连到新路径上
实现逻辑
不需要额外新建独立线程,所有地图UI操作必须在主线程执行,按以下逻辑修改即可:
- 新增全局变量:轨迹点列表、当前绘制的Polyline实例、Polyline样式配置
- 修正定位回调:仅在记录开启状态下把新坐标加入轨迹点列表,每次加完点更新Polyline的点集合,不需要等记录结束再统一渲染
- 修正启停逻辑:开始记录时清空旧轨迹点、重置Polyline对象、注册定位回调;停止记录时移除轨迹记录用的回调,保留普通定位更新当前位置即可
- 删除无效的临时回调注册代码,所有定位回调统一指定主线程Looper
修正后可直接运行的代码
替换原有GpsActivity代码即可,注意补全缺失的TAG常量:
public class GpsActivity extends FragmentActivity implements OnMapReadyCallback { private static final String TAG = "GpsActivity"; private static final int PRIORITY_HIGH_ACCURACY = 100; private GoogleMap mMap; private static final int PERMISSIONS_FINE_LOCATION = 99; Location loc; LatLng curPo; // 轨迹记录状态:true=正在记录 boolean state = false; private LocationRequest locationRequest; private FusedLocationProviderClient fusedLocationProviderClient; // 存储轨迹点的列表 private List<LatLng> trackPoints = new ArrayList<>(); // 当前正在绘制的轨迹线对象 private Polyline currentTrackPolyline; // 轨迹线样式配置 private PolylineOptions trackPolylineOption; Button rstart; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); locationRequest = LocationRequest.create() .setInterval(5000) .setPriority(PRIORITY_HIGH_ACCURACY) .setSmallestDisplacement(5); LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); builder.addLocationRequest(locationRequest); fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(GpsActivity.this); setContentView(R.layout.activity_gps); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); rstart = findViewById(R.id.rstart); rstart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { changeState(); } }); // 初始化轨迹样式:宽度10px、蓝色、按大地曲线绘制 trackPolylineOption = new PolylineOptions() .width(10f) .color(Color.BLUE) .geodesic(true); askforPermission(); } @SuppressLint("MissingPermission") @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; if(checkPermission()){ mMap.setMyLocationEnabled(true); } mMap.getUiSettings().setMyLocationButtonEnabled(true); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); if (checkPermission()) { Log.d(TAG, "onStart: register location update for current position"); // 非记录状态下只注册普通定位回调,用于更新当前位置 if (!state) { fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } if (mMap!=null) mMap.setMyLocationEnabled(true); } } @Override protected void onStop() { super.onStop(); if(fusedLocationProviderClient != null){ fusedLocationProviderClient.removeLocationUpdates(locationCallback); } } // 权限申请 private void askforPermission(){ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSIONS_FINE_LOCATION); } } } private boolean checkPermission() { int hasFineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION); return hasFineLocationPermission == PackageManager.PERMISSION_GRANTED && hasCoarseLocationPermission == PackageManager.PERMISSION_GRANTED; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){ case PERMISSIONS_FINE_LOCATION: if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ askforPermission(); if (checkPermission() && !state) { fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } }else{ Toast.makeText(this,"this App requires permission for gps", Toast.LENGTH_SHORT).show(); finish(); } } } // 更新相机位置到当前定位点 public void setCurrentLocation(Location location) { LatLng startLatLng = new LatLng(location.getLatitude(), location.getLongitude()); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(startLatLng, 15); mMap.moveCamera(cameraUpdate); } // 统一的定位回调 LocationCallback locationCallback = new LocationCallback() { @Override public void onLocationResult(@NonNull LocationResult locationResult) { super.onLocationResult(locationResult); List<Location> savedLocation = locationResult.getLocations(); if(savedLocation.size() > 0){ loc = savedLocation.get(savedLocation.size() - 1); curPo = new LatLng(loc.getLatitude(), loc.getLongitude()); Log.d(TAG,"OnLocationResult: Lat: " + loc.getLatitude() + "Long: " + loc.getLongitude()); setCurrentLocation(loc); // 记录状态下实时更新轨迹 if (state) { trackPoints.add(curPo); // 第一个点时创建Polyline,后续只更新点集合,避免重复绘制闪烁 if (currentTrackPolyline == null) { trackPolylineOption.addAll(trackPoints); currentTrackPolyline = mMap.addPolyline(trackPolylineOption); } else { currentTrackPolyline.setPoints(trackPoints); } } } } }; // 轨迹启停切换 public void changeState() { if(!state){ // 开启新记录:清空地图、清空旧轨迹点、重置轨迹对象 mMap.clear(); trackPoints.clear(); currentTrackPolyline = null; state = true; Toast.makeText(this, "Recording Start", Toast.LENGTH_SHORT).show(); if(checkPermission()){ // 先移除旧回调避免重复注册 fusedLocationProviderClient.removeLocationUpdates(locationCallback); // 指定主线程Looper,保证回调里可以直接操作地图 fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); addMarkerSingle(); } rstart.setText("Stop"); }else{ state = false; Toast.makeText(this, "Recording Stop", Toast.LENGTH_SHORT).show(); addMarkerSingle(); fusedLocationProviderClient.removeLocationUpdates(locationCallback); // 恢复普通定位更新当前位置 if (checkPermission()) { fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } rstart.setText("Start"); } } // 添加起止点Marker public void addMarkerSingle(){ if(checkPermission()){ fusedLocationProviderClient.getLastLocation() .addOnSuccessListener(new OnSuccessListener<Location>() { @Override public void onSuccess(Location location) { if (location == null) return; LatLng startEnd = new LatLng(location.getLatitude(), location.getLongitude()); mMap.addMarker(new MarkerOptions().position(startEnd)); } }); } } }
注意事项
- 所有操作GoogleMap的代码必须在主线程执行,注册定位回调时必须传
Looper.getMainLooper(),否则子线程操作UI不会生效,也不会抛出明确的报错 - 不要每次拿到新点就重新调用
addPolyline,会导致轨迹闪烁、性能差,只要持有Polyline实例,每次调用setPoints()更新点列表即可 - 开启新记录时必须清空轨迹点列表、重置Polyline对象,否则旧轨迹点会被连接到新路径上
- 定位间隔和最小位移可以根据需求调整,当前配置是5秒/5米更新一次,平衡功耗和轨迹流畅度
内容的提问来源于stack exchange,提问作者Barcode1998
相关产品推荐
相关产品推荐

