Android Maps V2 memory leak LocationClientHelper
Asked Answered
B

1

5

We're trying to track down a memory leak happening on the GoogleMap in our Android app, which ends in an OOM after about 40-50 device rotations. The map gets set around 3500 markers.

The App has a minSDK of 9 and therefore using the SupportMapFragment from the V4 Support Library.

We've tried multiple things including:

  • Caching the LatLng's
  • Caching CameraUpdates
  • Removing markers from map
  • Removing listeners from map
  • Removing all listeners, markers etc so that we just have a plain map
  • Updating Google Play Services library
  • Updating Support library

Analyzing the memory dump in MAT shows that we accumulate lots of instances of com.google.android.gms.location.internal.LocationClientHelper$ListenerTransport which we have no clue where they are coming from.

Anyone has an idea on what could be the cause of this memory leak?

The following code has already all markes and listeners removed and still leaks. First the base class:

public abstract class BaseMapFragment extends Fragment {

public static final int MENU_ITEM_ID_SEARCH= 102;
public static final int MENU_ITEM_ID_SHOW_LIST= 100;
public static final int ZOOM_LEVEL_DEFAULT= 14;

private static final String SAVED_INSTANCE_LATITUDE= "savedLatitude";
private static final String SAVED_INSTANCE_LONGITUDE= "savedLongitutde";
private static final String SAVED_INSTANCE_ZOOM= "savedZoom";

protected static final String CLASSTAG= BaseMapFragment.class.getSimpleName();

private GoogleMap mMap;
private CameraUpdate mResumeCameraUpdate= null;
private double mSavedLatitude;
private double mSavedLongitude;
private float mSavedZoom;
private static View mView;

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (mMap != null) {
        outState.putDouble(SAVED_INSTANCE_LATITUDE, mMap.getCameraPosition().target.latitude);
        outState.putDouble(SAVED_INSTANCE_LONGITUDE, mMap.getCameraPosition().target.longitude);
        outState.putFloat(SAVED_INSTANCE_ZOOM, mMap.getCameraPosition().zoom);
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    if (savedInstanceState != null) {
        mSavedLatitude= savedInstanceState.getDouble(SAVED_INSTANCE_LATITUDE, Constants.EXTRA_VALUE_NONE);
        mSavedLongitude= savedInstanceState.getDouble(SAVED_INSTANCE_LONGITUDE, Constants.EXTRA_VALUE_NONE);
        mSavedZoom= savedInstanceState.getFloat(SAVED_INSTANCE_ZOOM, Constants.EXTRA_VALUE_NONE);
    }

    if (mView != null) {
        ViewGroup parent= (ViewGroup) mView.getParent();
        if (parent != null)
            parent.removeView(mView);
    }
    try {
        mView= inflater.inflate(R.layout.map_layout, container, false);
    } catch (InflateException e) {
        /* map is already there, just return view as it is */
    }
    return mView;
}

protected GoogleMap initializeMap() {
    if (mMap != null) {
        if (mSavedLatitude != Constants.EXTRA_VALUE_NONE && mSavedLatitude != 0.0) {
            mResumeCameraUpdate= Context.getCamUpdate(mSavedZoom, mSavedLatitude, mSavedLongitude);
        } else {
            mResumeCameraUpdate= Context.getCamUpdate(mMap.getCameraPosition().zoom, mMap.getCameraPosition().target.latitude, mMap.getCameraPosition().target.longitude);
        }
    }

    SupportMapFragment mapFragment= (SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map);
    if (mapFragment == null) {
        mapFragment= (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map);
        if (mapFragment == null) {
            MapsInitializer.initialize(getActivity());
            mapFragment= SupportMapFragment.newInstance();
            mMap= mapFragment.getMap();
        } else {
            mMap= mapFragment.getMap();
        }
    } else {
        mMap= mapFragment.getMap();
    }

    // check if map is created successfully or not
    if (mMap == null) {
        Toast.makeText(getActivity().getApplicationContext(), R.string.map_create_unable, Toast.LENGTH_SHORT).show();
    } else {
        mMap.setMyLocationEnabled(true);
        mMap.setOnMyLocationButtonClickListener(new OnMyLocationButtonClickListener() {
            @Override
            public boolean onMyLocationButtonClick() {
                if (mMap.getMyLocation() != null) {
                    CameraUpdate newLatLngZoom= Context.getCamUpdate(ZOOM_LEVEL_DEFAULT, mMap.getMyLocation());
                    mMap.animateCamera(newLatLngZoom);
                } else {
                    Toast.makeText(getActivity().getApplicationContext(), R.string.map_location_services_disabled, Toast.LENGTH_SHORT).show();
                }
                return true;
            }
        });

    }
    return mMap;
}

}

Subclass

public class MySupportMapFragment extends BaseMapFragment {

private LinearLayout mStaoButtonsLayout;
private ToggleButton mStaoButton;
private ToggleButton mGasStaoButton;

private Boolean mInitialLocationChange;
private CameraUpdate mResumeCameraUpdate;
private GoogleMap mMap;
private double mBundleLatitude;
private double mBundleLongitude;


@Override
public void addRequiredModelClasses(LinkedHashSet<Class<? extends ComergeModel<?>>> set) {
    set.add(AboModel.class);
    set.add(StationModel.class);
    super.addRequiredModelClasses(set);
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putDouble(BUNDLE_EXTRA_CENTER_LATITUDE, mBundleLatitude);
    outState.putDouble(BUNDLE_EXTRA_CENTER_LONGITUDE, mBundleLongitude);        
}


@Override
public void onActivityCreated(final Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setHasOptionsMenu(showSearchButton());

    final StationModel stationModel= getContext().getModel(StationModel.class);

    mStaoButtonsLayout= (LinearLayout) getActivity().findViewById(R.id.mapStaoButtons);
    mStaoButtonsLayout.setVisibility(View.VISIBLE);
    mStaoButton= (ToggleButton) mStaoButtonsLayout.findViewById(R.id.staoButton);
    mStaoButton.setChecked(stationModel.isStationButtonChecked());
    mGasStaoButton= (ToggleButton) mStaoButtonsLayout.findViewById(R.id.gasStaoButton);
    mGasStaoButton.setChecked(stationModel.isGasStationButtonChecked());

    mMap= initializeMap();
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    addSearchButton(menu);
}

}
Budd answered 30/7, 2015 at 14:31 Comment(2)
Is this possibly a memory leak in one of the google classes?Suffolk
Google Maps issue opened: code.google.com/p/gmaps-api-issues/issues/…Suffolk
S
9

I had a similar problem before. I added following code to solve my problem:

@Override 
public void onDestroy() {
     if (mMap != null) {
         mMap.setMyLocationEnabled(false);
     } 
 }

It seems that LocationClientHelper$ListenerTransport is related to setMyLocationEnabled(). I had to unregister some callbacks to prevent memory leak.

Sectarian answered 6/8, 2015 at 16:31 Comment(7)
You my friend, deserve a medal! Thanks! Additionally, we nullified the listeners that are set on the map and it seems to have solved the problem!Budd
Dude, you are awesome! Would give you hundreds of upvotes for this! We'll as well report this to google as this is relly odd!Suffolk
Thank you for sharing this! This finally fixes the leak! You deserve a hug from us :)Suffolk
Thank you for reporting this issue to Google. Hope they can fix or at least note this issue on wiki. And thanks to darijan for fixing my poor English. :)Karakorum
do not forget also to call super.onDetach(); at the end or you will get an Exception..Kinney
@Kinney What do you exactly mean by that? We should put this code into onDetach as well? If yes, you're actually totally rightSuffolk
I also found that setting my GoogleMap object to null caused memory leaks even after I thought I'd deregistered all listeners and detached its associated objects. Be careful when setting your mMap equivalent to null!Flair

© 2022 - 2024 — McMap. All rights reserved.