【问题标题】:google maps api v2 out of memory error谷歌地图 api v2 内存不足错误
【发布时间】:2015-07-25 19:01:02
【问题描述】:

我的应用程序存在巨大的内存问题。我正在使用带有ClusterManager 和自定义标记的google map api v2。我通过调用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)

我的Bitmaps 有一个LruCache 对象,这意味着我不会重新创建它们,而是重复使用它们。我可以清楚地看到每个Bitmap 对象都是从缓存中获取的,而不是从其他地方获取的。但是,如果Bitmap 尚未在缓存中(第一次加载),我会从我的应用程序的内部存储中加载它,但它只会在第一次加载Bitmap 时发生。我将LruCache 的实例保留在保留的Fragment 实例中,并在每次重新创建Activity 并且需要重新绘制地图时将其传递给我的自定义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&lt;MyItam&gt; 扩展markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)); 的这一行发生了OOM。

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

【问题讨论】:

  • 你在你的主要活动中试过这些吗? @Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory();我的意思是在地图视图添加的活动或片段中......
  • 评论已编辑..检查...
  • 不,我没有,但我试过System.gc()。它没有帮助......我猜它们几乎是一样的。
  • 试试这个代码,让我知道结果......最好......
  • 只是添加我的评论以供将来任何人查找 - System.gc() 似乎没有任何区别。查看 Android Studio 中的分析器,在调用 gc() 之前,“图形”占用了 198.1MB,在 gc() 之后是 198.2MB,所以没有区别。然而,调用 map.clear() 产生了巨大的变化。在 map.clear() 之前,“图形”为 198.1MB,在 clear() 之后变为 73.1MB,大幅减少。

标签: android bitmap out-of-memory google-maps-android-api-2 markerclusterer


【解决方案1】:

我在尝试一次在演示模式下运行应用几个小时时遇到了这个问题。无论我尝试什么,30 分钟后,我都会看到没有可读堆栈报告的崩溃。

我尝试了 System gc()、分离片段、单例活动、将 google play 服务更新到最新版本、清除对覆盖的引用、将地图生命周期附加到活动等等。 经过多次失败的尝试和很多挫折后,我终于找到了可行的方法。这不是对地图错误的修复,但它可以防止我的应用程序崩溃:

    <application
    ...
    android:largeHeap="true">

【讨论】:

  • 我花了几个小时才碰到这个帖子。我使用了三个答案的组合:largeHeap、System.gc 和 map.clear。成功了
  • android:largeHeap="true" 将有助于增加应用程序的内存使用量,但无助于解决您的问题。如果没有 largeHeap 意味着 largeHeap false,您的应用可以运行 30 分钟。因此,如果 largeHeap 为 true,就可以让您的应用持续更长时间而不会崩溃,就像一两个小时......等等。
  • 正确,这就是我说“这不是修复”的原因。
【解决方案2】:

好吧...我认为这应该可行...

只要您想重置地图,请添加googleMap.clear()

希望这会对你有所帮助。

【讨论】:

  • 也已经尝试过了。没有运气。我什至尝试将每个标记的图标分别设置为默认值,然后在ActivityonDestroy() 中一一删除。
猜你喜欢
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-18
  • 2013-04-16
相关资源
最近更新 更多