Ruben
Ruben

Reputation: 1749

Google Maps Android API extremely bad performance

I'm currently trying to write an app that shows several locations on a map using markers. Even when working with only a relatively 'small' set of Markers (I've seen StackOverflow issues where people want to draw more than 100k), I get terrible performance and my map takes a few seconds to move.

I also have a screenshot of my GPU profiling, and I'm wondering if anyone can understand why these calls are so extremely high:

Screenshot of GPU profiling on-screen

I have tried to profile my application using the DDMS, but this doesn't seem to give me any conclusive answers.

The code I'm using is as follows:

tab_map.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:id="@+id/city_map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize" />
</FrameLayout>

LocationFragment.java

/* a whole list of imports and package definition */

/**
 * Created by Ruben on 05-08-17.
 */

public class LocationsFragment extends Fragment
        implements OnMapReadyCallback {
    private static final String TAG = LocationsFragment.class.getSimpleName();

    private LocationsFragment instance;

    private City city;
    private List<Marker> markers;
    private List<Location> locations;

    private Target iconTarget;

    private GoogleMap map;
    private SupportMapFragment mapFragment;
    private BitmapDescriptor markerIcon;
    private Bitmap markerIconBitmap;

    private ApiService apiService;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.tab_map, null);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceStace) {
        super.onActivityCreated(savedInstanceStace);

        // Set up the view
        instance = this;

        mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.city_map);
        apiService = RestClient.getClient(getContext()).create(ApiService.class);
        city = getArguments().getParcelable("city");
        markers = new ArrayList<>();

        iconTarget = new Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                markerIconBitmap = bitmap;
                updateMarkerIcons();
            }

            @Override
            public void onBitmapFailed(Drawable errorDrawable) {
            }

            @Override
            public void onPrepareLoad(Drawable placeHolderDrawable) {

            }
        };

        // Start async processes
        // Load Google map
        mapFragment.getMapAsync(this);

        // Load locations from API
        getLocations();

        // Load custom marker for city
        getCityMarker();

    }

    private void getCityMarker() {
        Picasso
                .with(getContext())
                .load(city.getMarker().getUrl())
                .resize(ScreenUtils.convertDIPToPixels(getContext(), 24), ScreenUtils.convertDIPToPixels(getContext(), 24))
                .into(iconTarget);
    }

    @Override
    public void onMapReady(GoogleMap map) {
        this.map = map;

        if(markerIconBitmap == null) {
            markerIcon = BitmapDescriptorFactory.fromAsset("default1.png");
        }else{
            markerIcon = BitmapDescriptorFactory.fromBitmap(markerIconBitmap);
        }

        if (ContextCompat.checkSelfPermission(this.getContext(), "ACCESS_FINE_LOCATION") == PackageManager.PERMISSION_GRANTED) {
            map.setMyLocationEnabled(true);
        } else {
            Toast.makeText(instance.getContext(), getString(R.string.no_location_permission), Toast.LENGTH_LONG).show();
        }

        map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(city.getCenter().getLat(), city.getCenter().getLong()), 12));

        setMarkers();
    }

    public void getLocations() {
        Call<LocationsResponse> call = apiService.getLocations(city.getId());
        call.enqueue(new Callback<LocationsResponse>() {
            @Override
            public void onResponse(Call<LocationsResponse> call, Response<LocationsResponse> response) {
                if(response.code() != 200) {
                    Toast.makeText(instance.getContext(), getString(R.string.retrieving_locations) + "1", Toast.LENGTH_LONG).show();
                    return;
                }

                Log.d(TAG, "Received locations: " + response.body().getLocations().size());
                locations = response.body().getLocations();

                setMarkers();
            }

            @Override
            public void onFailure(Call<LocationsResponse> call, Throwable t) {
                t.printStackTrace();
                Toast.makeText(instance.getContext(), getString(R.string.retrieving_locations) + "1", Toast.LENGTH_LONG).show();
            }
        });
    }

    public void setMarkers() {
        Log.d(TAG, "Calling setMarkers");
        if(map != null && locations != null) {
            // Remove all the old markers
            if(!markers.isEmpty()) {
                int numMarkers = markers.size();
                for(int i = 0; i < numMarkers; i++) {
                    markers.get(i).remove();
                }
                markers.clear();
            }

            // Create a new marker for each location
            int numLocations = locations.size();
            Log.d(TAG, "Setting markers for " + numLocations + " locations");
            for(int i = 0; i < numLocations; i++) {
                Location location = locations.get(i);
                markers.add(map.addMarker(
                    new MarkerOptions()
                        .position(new LatLng(location.getPosition().getLat(), location.getPosition().getLong()))
                        //.icon(markerIcon)
                        //.title(location.getName())
                        //.snippet(getString(R.string.location_marker_text, NumberFormat.getInstance().format(location.getStones())))
                ));
            }
        }
    }

    public void updateMarkerIcons() {
        Log.d(TAG, "Calling updateMarkerIcons");
        if(map != null && markerIconBitmap != null) {
            markerIcon = BitmapDescriptorFactory.fromBitmap(markerIconBitmap);
        }

        if(map != null && !markers.isEmpty()) {
            int numMarkers = markers.size();
            for(int i = 0; i < numMarkers; i++) {
                Marker marker = markers.get(i);
                //marker.setIcon(markerIcon);
            }
        }
    }

    @Override
    public void onDestroyView() {
        Picasso.with(this.getContext()).cancelRequest(iconTarget);

        mapFragment.onDestroyView();

        super.onDestroyView();
    }

    public void updateLocations() {
        if(locations != null) {
            getLocations();
        }
    }

    public void setCity(City city) {
        this.city = city;
        getCityMarker();
    }
}

As you can see, I uncommented some lines where I set the .snippet or .icon for the markers, as I suspected this might have been the issue. Sadly, this didn't seem to be the problem.

Using breakpoints, I tried to confirm that none of my functions where I set / edit markers are being called after they should, and I found out that this is definitely not the case. Once the markers are set, no additional code is executed in this file. All my other activities seem to run fine, so I'm starting to believe it's an issue related to Google Maps. What is worth noting, is that when I move away from the markers, meaning not being in the boundaries of my screen anymore, the performance is decent and scrolling looks smooth.

Upvotes: 3

Views: 1569

Answers (1)

Fred B.
Fred B.

Reputation: 1721

Sorry for the late answer, you probably have found a solution but it might help other people.

Have you considered Clusters?

The problem you have is that it requires a lot of resources to render all those markers on the map, and the the api is re-rendering every time your view moves.

When a lot of pins are really close together, you cannot really differentiate them from one another. By default, clusters will group them according to the zoom on your map to optimize rendering and visualization. If the default behavior or look do not fit your tastes, you can easily override it.

Hope I helped.

Upvotes: 1

Related Questions