google maps api v2 out of memory error

2020-05-25 07:34发布

问题:

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.

回答1:

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">


回答2:

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

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

Hope this will help you.