Google Maps Android API v2 SupportMapFragment memory leak
Asked Answered
T

4

18

Using 2 simple activities. First Activity which only holds a button to start the 2nd Activity which holds the map:

Main Activity:

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

public void goToMap(View view){ //This is just the onClick method for the button
    Intent intent=new Intent( this, BigMapTest.class);
    startActivity(intent);
}

The map activity:

public class BigMapTest extends FragmentActivity {
SupportMapFragment mapFragment;
GoogleMap map;

@Override
protected void onCreate(Bundle arg0) {
    // TODO Auto-generated method stub
    super.onCreate(arg0);

    setContentView(R.layout.travel_diary_big_map);

    mapFragment=(SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.big_map);
    map=mapFragment.getMap();

}

The XML Layout for the map activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<fragment
        android:id="@+id/big_map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="com.google.android.gms.maps.SupportMapFragment"  
        />

Now when I run this code, pressing the button to move to the Activity with the map, and pressing back to get to the first activity...then repeating the process, I can see the heap increasing in size each time, until it reaches it's limits and then it starts clamping. If you mess around a bit more with map(i.e. zooming) I can get an OOM Exception at this point.

01-25 16:10:13.931: D/dalvikvm(21578): GC_FOR_ALLOC freed 1898K, 7% free 45859K/49187K, paused 204ms
01-25 16:10:14.671: I/dalvikvm-heap(21578): Clamp target GC heap from 52.724MB to 48.000MB
01-25 16:10:14.671: D/dalvikvm(21578): GC_CONCURRENT freed 2534K, 6% free 46554K/49187K, paused 3ms+14ms
01-25 16:10:15.372: I/dalvikvm-heap(21578): Clamp target GC heap from 52.979MB to 48.000MB
01-25 16:10:15.382: D/dalvikvm(21578): GC_CONCURRENT freed 2273K, 5% free 46815K/49187K, paused 3ms+15ms
01-25 16:10:15.622: I/dalvikvm-heap(21578): Clamp target GC heap from 52.604MB to 48.000MB
01-25 16:10:15.622: D/dalvikvm(21578): GC_FOR_ALLOC freed 657K, 6% free 46431K/49187K, paused 202ms
01-25 16:10:16.203: I/dalvikvm-heap(21578): Clamp target GC heap from 52.959MB to 48.000MB
01-25 16:10:16.203: D/dalvikvm(21578): GC_FOR_ALLOC freed 1469K, 5% free 46796K/49187K, paused 217ms
01-25 16:10:16.203: I/dalvikvm-heap(21578): Forcing collection of SoftReferences for 278744-byte allocation
01-25 16:10:16.423: I/dalvikvm-heap(21578): Clamp target GC heap from 52.952MB to 48.000MB
01-25 16:10:16.423: D/dalvikvm(21578): GC_BEFORE_OOM freed 9K, 5% free 46786K/49187K, paused 219ms
01-25 16:10:16.423: E/dalvikvm-heap(21578): Out of memory on a 278744-byte allocation.

Any suggestions/help would be appreciated.

Taboo answered 25/1, 2013 at 14:36 Comment(10)
Use MAT to determine the source of your leak.Oneiric
Using MAT and then searching for the object with the biggest retained size (which keeps growing with every cycle) then using path to GC roots excluding weak references I have reached the following: class maps.by.a @ 0x414f7fa8 System Class. - But i don't know what to do next.Taboo
It seems something is accumilating in a Hashmap: The class "maps.by.a", loaded by "dalvik.system.PathClassLoader @ 0x413de740", occupies 12,923,112 (57.78%) bytes. The memory is accumulated in one instance of "java.util.HashMap$HashMapEntry[]" loaded by "<system class loader>". Keywords java.util.HashMap$HashMapEntry[] maps.by.a dalvik.system.PathClassLoader @ 0x413de740Taboo
Well, that doesn't sound promising. Any chance I can con you into uploading the entire test project somewhere so I can take a look at it?Oneiric
Sure. Though I'm very new to this...Where would it be appropriate to load it too? (Though the project is very small, as described above)Taboo
GitHub repo, ZIP file on public DropBox, ZIP file on Amazon S3, ...Oneiric
dl.dropbox.com/u/83195107/MapMemoryLeakTest.zipTaboo
OK, I will take a look at this over the weekend and will report back with results.Oneiric
Google have acknowledged this problem - and stated it will be fixed with the next release of of the Maps Android API. code.google.com/p/gmaps-api-issues/issues/detail?id=4766Taboo
@Taboo Question is - when is next release?Predesignate
O
9

Near as I can tell from some basic MAT sleuthing, what you are seeing is a cache maintained by Maps V2 of downloaded map data. The cache seems to be bigger if you pan and zoom a lot. The cache shrinks if you leave the map and return to a fresh map later on. I could not get N caches from N times launching the map activity of your sample app, and the cache size ebbed and flowed depending upon what the user did.

Alas, this cache is unconfigurable, AFAIK, in terms of how big it is, when it gets cleared out, does it spill over to disk, etc.

So, by default, all you can do is leave aside a healthy chunk of your heap space for Maps V2 to play with, and take steps to stay within this smaller subset of heap.

If you wanted to experiment, you could try calling clear() on GoogleMap, or onLowMemory() on your SupportMapFragment, to see if any help reduce this cache size.

Oneiric answered 26/1, 2013 at 15:10 Comment(1)
While some of the observed behavior might be due to cache ebb and flow, the maps fragment is definitely leaking the context hosting it. This can be easily tested by rotating an activity with a map a few times and then seeing how many copies of the activity are still in the heap. Even the maps demo app does this as described in the bug report.Kersey
T
2

Android Developers 5:10 PM - Google+ Page

We’re rolling out Google Play services v3.0, which introduces Google+ Sign-In and Google Maps Android API improvements.

As of 26 February 2013 this issue which is also described on the gmaps-api-issues page has now been fixed by the current Google API Update.

Typify answered 26/2, 2013 at 21:58 Comment(0)
T
1

I have exactly the same problem. The memory gets increased every time the activity hosting the V2 map starts. And it does not get released even when the activity finishes.

So the workaround is to reuse that activity. Make the activity singleTask in manifest and instead of finish() it, use moveTaskToBack(true);

Thankless answered 30/1, 2013 at 9:13 Comment(1)
This is a good solution if you have only 1 map or very few maps in use, but i don't see how this would work if you have maps in several activities.Taboo
G
-1

Use this in your layout:

<LinearLayout
        android:id="@+id/map_container2"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_weight="35.22"
        android:orientation="horizontal" >

        <fragment
            android:id="@+id/map1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            class="com.google.android.gms.maps.SupportMapFragment"
            map:cameraTargetLat="40.72"
            map:cameraTargetLng="-74.00"
            map:cameraZoom="8" />
    </LinearLayout>

And this code:

onCreate{
   setUpMapIfNeeded();
}

private void setUpMapIfNeeded() {
        // TODO Auto-generated method stub
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map1))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }

private void setUpMap() {
        // TODO Auto-generated method stub
         // Hide the zoom controls as the button panel will cover it.
        mUiSettings = mMap.getUiSettings();
 // Enables/disables zoom gestures (i.e., double tap, pinch & stretch).
        mMap.getUiSettings().setZoomGesturesEnabled(false);
// Enables/disables scroll gestures (i.e. panning the map).
        mMap.getUiSettings().setScrollGesturesEnabled(false);
 // Enables/disables the compass (icon in the top left that indicates the orientation of the
        // map).
        mMap.getUiSettings().setCompassEnabled(false);
        // Add lots of markers to the map.
        addMarkersToMap();

        // Pan to see all markers in view.
        // Cannot zoom to bounds until the map has a size.
        final View mapView = getSupportFragmentManager().findFragmentById(R.id.map1).getView();
        if (mapView.getViewTreeObserver().isAlive()) {
            mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @SuppressLint("NewApi") // We check which build version we are using.
                @Override
                public void onGlobalLayout() {
                    LatLngBounds bounds = new LatLngBounds.Builder()
                            .include(WOODS)
                            .build();
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                      mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                      mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
                }
            });
        }
    }

private void addMarkersToMap() {
        // TODO Auto-generated method stub
         // Uses a colored icon.
        mWoods = mMap.addMarker(new MarkerOptions()
                .position(WOODS)
                .title("Woods")
                .snippet("R. Quatá, 1016, Vila Olimpia - (11) 3849-6868")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
    }
Gimbals answered 25/1, 2013 at 14:44 Comment(3)
What is WOODS here LatLngBounds bounds = new LatLngBounds.Builder() .include(WOODS) and variable name for mUiSettingsJoon
WOODS is private static final LatLng WOODS = new LatLng(-23.596089,-46.682393); the variable for Lat and Long value, private UiSettings mUiSettings;Gimbals
Can you explain why that removes leaks?Swipe

© 2022 - 2024 — McMap. All rights reserved.