17

i have a huge memory problem in my app. i am using google map api v2 with ClusterManager and custom markers. i supply an image via call to markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)); for each marker based on its category. the problem is: after several screen rotations my app crashes because of OOM error:

05-14 11:04:12.692  14020-30201/rokask.rideabike E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 4194316 byte allocation with 1627608 free bytes and 1589KB until OOM"
05-14 11:04:12.722  14020-30201/rokask.rideabike E/AndroidRuntime﹕ FATAL EXCEPTION: GLThread 19179
Process: rokask.rideabike, PID: 14020
java.lang.OutOfMemoryError: Failed to allocate a 4194316 byte allocation with 1627608 free bytes and 1589KB until OOM
        at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
        at android.graphics.Bitmap.nativeCreate(Native Method)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:939)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:912)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:879)
        at com.google.maps.api.android.lib6.gmm6.n.c.i.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.c.l.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.c.l.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.c.l.b(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.c.b.ak.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.c.b.as.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.x.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.l.a(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.l.b(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.cv.f(Unknown Source)
        at com.google.maps.api.android.lib6.gmm6.n.cv.run(Unknown Source)

i have a LruCache object with my Bitmaps, it means that i do not recreate them, but reuse them. i can clearly see that every Bitmap object is taken from the cache, not from elsewhere. However, if the Bitmap is not in the cache yet (first time loading) i load it from my app's internal storage, but it only happnes when the Bitmap is laoded the first time. i keep the instance of the LruCache in a retained Fragment instance and pass it to my custom DefaultClusterRenderer<MyObject> object everytime the Activity is recreated and map needs to be redrawn.

this is my DefaultClusterRenderer<MyItem> extension:

public class DotRenderer extends DefaultClusterRenderer<Dot> {
private final String internalStorageDir;
private final LruCache<String, Bitmap> lruCache;

public DotRenderer(Context context, GoogleMap googleMap, ClusterManager<Dot> clusterManager,
                   LruCache<String, Bitmap> lruCache, String internalStorageDir)
{
    super(context, googleMap, clusterManager);
    //this.bitmaps = bitmaps;
    this.internalStorageDir = internalStorageDir;
    this.lruCache = lruCache;
}

@Override
protected void onBeforeClusterItemRendered(Dot mapObject, MarkerOptions markerOptions) {
    markerOptions.title(mapObject.getTitle());
    String id = Integer.toString(mapObject.getTypeId());
    //
    Bitmap bitmap = getBitmapFromMemCache(id);
    if (bitmap == null) {
        Log.d(MainActivity.LOG_TAG, "reading bitmap from storage.");
        Map.Entry<String, Bitmap> bitmapEntry
                = BitmapManager.getBitmapFromStorage(internalStorageDir, id);
        if (bitmapEntry != null) {
            markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmapEntry.getValue()));
            addBitmapToMemCache(id, bitmapEntry.getValue());
        }
    } else {
        Log.d(MainActivity.LOG_TAG, "reading bitmap from cache.");
        markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));
    }
}

private void addBitmapToMemCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        lruCache.put(key, bitmap);
    }
}

private Bitmap getBitmapFromMemCache(String key) {
    return lruCache.get(key);
}
}

this is the code inside my Activity where i start loading the map (this code is executed everytime screen orientation changes):

    ClusterManager<Dot> clusterManager = new ClusterManager<>(this, googleMap);
    clusterManager.setOnClusterItemInfoWindowClickListener(
            new ClusterManager.OnClusterItemInfoWindowClickListener<Dot>() {
                @Override
                public void onClusterItemInfoWindowClick(Dot dot) {
                    int id = dot.getId();
                    String title = dot.getTitle();
                    Log.d(LOG_TAG, "clicked marker with id " + id
                            + " and title " + title + ".");
                    Intent infoWindowActivityIntent =
                            new Intent(MainActivity.this, InfoWindowActivity.class);
                    infoWindowActivityIntent.putExtra("dotId", id);
                    infoWindowActivityIntent.putExtra("dotTitle", title);
                    startActivity(infoWindowActivityIntent);
                }
            });
    googleMap.setOnCameraChangeListener(clusterManager);
    googleMap.setOnInfoWindowClickListener(clusterManager);

    DotRenderer dotRenderer =
            new DotRenderer(getApplicationContext(), googleMap, clusterManager,
                    lruCache, this.getFilesDir().toString());
    clusterManager.setRenderer(dotRenderer);

the memory keeps increasing with every screen rotation, the more i zoom in the map (the more markers are shown) the more amount of memory is added to my app's heap when i rotate the screen until application crashes.

sometimes the error is not like above, but shows that OOM happened at this line in my DefaultClusterRenderer<MyItam> extension markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));.

if i disable custom marker icon (remove all the Bitmap related code) the memory problem vanishes. please help me to find what causes this OOM to appear.

Salivan
  • 1,876
  • 7
  • 26
  • 45
  • Did u tried these on your Main Activity? @Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory(); } I mean in the activity or fragment, which the mapview added... – Rishad Appat May 14 '15 at 08:52
  • comment edited.. check it... – Rishad Appat May 14 '15 at 08:54
  • no, i didn't, but i tried `System.gc()`. it doesn't help... i gues they are pretty much the same. – Salivan May 14 '15 at 08:54
  • Try this code and let me knw the result... Al the best.... – Rishad Appat May 14 '15 at 08:54
  • i don't think this is a clean workaround. i don't even use `MapView`. i use `GoogleMap` – Salivan May 14 '15 at 08:55
  • try this code in your manifest.... android:largeHeap = true – Rishad Appat May 14 '15 at 10:05
  • i've already tried it, but it only makes the app last longer until it still finally crashes... – Salivan May 14 '15 at 10:19
  • 1
    Just adding my comment for anyone in future to find - System.gc() does not seem to be making any difference. Looking at the profiler in Android Studio, before calling gc(), "graphics" was occupying 198.1MB, after gc() 198.2MB, so no difference. However, calling map.clear() made a huge difference. Before map.clear(), "graphics" was 198.1MB and after clear() became 73.1MB, a major reduction. – vepzfe Jun 28 '20 at 06:10

2 Answers2

2

I ran into this problem trying to run an app on demo mode for a few hours at a time. No matter what I tried, after 30 minutes, I would see this crash without a readable stack report.

I tried System gc(), detaching the fragment, singleton activities, updating google play services to the latest, clearing references to overlays, attaching map lifecycle to activity and what not. After many failed attempts and a lot of frustration I finally found something that worked. It's not a fix for the map bug, but it kept my app from crashing:

    <application
    ...
    android:largeHeap="true">
TacoEater
  • 2,115
  • 20
  • 22
  • 1
    I spent hours before bumping into this thread. I used a combination of the three answers: largeHeap, System.gc and map.clear. It worked – FabioC Jul 18 '17 at 06:12
  • android:largeHeap="true" will help increase memory usage for your app, and won't help to resolve your problem. Without largeHeap mean largeHeap false your app can stand for 30 minutes. So with largeHeap true just let's your app last a bit longer without crash just like one or two hours ...etc. – Sarith Nob Feb 15 '21 at 04:50
  • Correct, that's why I said "It's not a fix". – TacoEater Feb 16 '21 at 13:43
1

Alright... I think this should work...

Add googleMap.clear() whenever you want to reset the map.

Hope this will help you.

Samurai
  • 3,724
  • 5
  • 27
  • 39
Rishad Appat
  • 1,786
  • 1
  • 15
  • 30
  • already tried this too. no luck. i've even tried setting the icon to default for each marker individually and then remove them one by one in `Activity`'s `onDestroy()`. – Salivan May 14 '15 at 10:25