谷歌地图API V2内存不足错误

17

我在我的应用程序中遇到了一个内存问题。我正在使用Google地图API V2和ClusterManager以及自定义标记。我为每个标记提供一个基于其类别的图像,通过调用markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));来实现。问题是:在多次屏幕旋转后,我的应用程序由于OOM错误而崩溃:

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)
我有一个带有LruCache对象的Bitmap,这意味着我不会重新创建它们,而是重复使用它们。我可以清晰地看到每个Bitmap对象都是从缓存中获取的,而不是其他地方获取的。但是,如果Bitmap尚未在缓存中(第一次加载),我会从我的应用程序内部存储加载它,但仅在第一次加载Bitmap时才发生这种情况。我将LruCache实例保留在一个保留的Fragment实例中,并在每次重新创建活动并需要重新绘制地图时将其传递给我的自定义DefaultClusterRenderer<MyObject>对象。
这是我的DefaultClusterRenderer<MyItem>扩展:
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);
}
}

这是我Activity中启动加载地图的代码(每次屏幕方向更改时都会执行此代码):

    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);

每次旋转屏幕,地图上显示的标记越多(缩放程度越高),应用程序的堆中就会添加更多的内存,直到应用程序崩溃。

有时错误信息不像上面那样,而是在我的 DefaultClusterRenderer<MyItam> 扩展中显示 OOM 发生在这行:markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));

如果禁用自定义标记图标(删除所有与 Bitmap 相关的代码),内存问题就会消失。请帮我找出导致 OOM 出现的原因。


评论已编辑,请检查。 - Rishad Appat
没有,但我尝试了System.gc(),它没什么帮助... 我猜它们差不多。 - Salivan
我认为这不是一个干净的解决方法。我甚至不使用 MapView,我使用 GoogleMap - Salivan
我已经尝试过了,但它只能让应用程序持续更长时间,直到最终崩溃... - Salivan
1
只是为了方便以后的查找,我想添加我的评论 - System.gc() 似乎没有产生任何影响。在调用 gc() 之前,在 Android Studio 的分析器中查看,“graphics” 占用了 198.1MB,在 gc() 之后变成了 198.2MB,所以没有任何区别。然而,调用 map.clear() 产生了巨大的影响。在调用 clear() 之前,“graphics” 是 198.1MB,在清除之后变成了 73.1MB,这是一个重大的减少。 - vepzfe
显示剩余3条评论
2个回答

2
我在尝试长时间运行应用程序的演示模式时遇到了这个问题。无论我尝试什么,30分钟后,我都会看到这个崩溃,但没有可读的堆栈报告。
我尝试了System gc(),分离片段,单例活动,将Google Play服务更新到最新版本,清除对覆盖层的引用,将地图生命周期附加到活动中等等。经过多次失败的尝试和大量的挫折后,我终于找到了一些有效的方法。这不是修复地图错误的方法,但它可以防止我的应用程序崩溃:
    <application
    ...
    android:largeHeap="true">

1
我在这个线程上花了好几个小时才找到解决方法。我结合了三个答案:largeHeap、System.gc和map.clear。最终成功了。 - FabioC
android:largeHeap="true" 可以帮助增加您的应用程序的内存使用量,但不会解决您的问题。如果没有设置 largeHeap 为 true,则您的应用程序可以运行 30 分钟左右。因此,将 largeHeap 设置为 true 可以让您的应用程序在不崩溃的情况下持续更长时间,例如一两个小时等。 - Sarith Nob
正确,这就是我说“这不是一个修复”的原因。 - TacoEater

1

好的...我想这应该可以工作...

每当您想重置地图时,请添加googleMap.clear()

希望这能帮到您。


已经尝试过这个了,但没有成功。我甚至尝试了为每个标记单独设置默认图标,然后在“Activity”的“onDestroy()”中逐个删除它们。 - Salivan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接