You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GoogleMap上实时绘制可重复记录的行走轨迹Polyline

问题根因

实时轨迹绘制失败是三个核心逻辑问题导致的:

  1. 缺少轨迹存储和绘制对象:没有定义列表保存历史轨迹点,也没有初始化Polyline实例,拿到新坐标后没有载体可以绘制路径
  2. 定位回调逻辑混乱:askforPermission方法里注册完定位回调立刻就执行了移除操作,等于注册无效;注册回调时没有指定主线程Looper,回调默认在子线程执行,直接操作地图不会生效
  3. 状态控制有漏洞:停止记录后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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:21:18