启用了largeHeap的位图回收

时间:2012-10-03 20:42:20

标签: android android-image android-memory android-largeheap

在启用largeHeap选项之前,我正在处理大型位图,它几乎消耗了应用程序可用的整个内存,并且通过导航回收它并加载新的内存几乎可以在完整的堆上运行。但是,当某些操作需要更多内存时,应用程序崩溃。所以我让largeHeap=true有更多的内存。

但这样做有一个意想不到的行为,看起来recycle()位图方法大部分时间都不起作用,而且应用程序在58Mb内存中工作(并且超过有时抛出OutOfMemoryException )现在以指数方式消耗内存并且不断增长(现在测试我做了231Mb分配的内存),预期的行为是内存管理继续工作,应用程序不会使用超过60Mb。

我该如何避免?还是有效地回收位图?

编辑:实际上,我在设备上分配超过390Mb的内存时给它OutOfMemoryError。 读取GC_ *日志显示只有GC_FOR_ALLOC有时会释放3.8Mb,但几乎从未在其他GC运行中释放过某些内容。

5 个答案:

答案 0 :(得分:21)

您应该查看Displaying Bitmaps Efficiently,其中包括有效处理大型位图的几种方法,

  • 有效加载大型位图
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;

这将在下载之前为您提供图片的大小,在此基础上,您可以检查设备的尺寸,并使用文档说明中提供的calculateInSampleSize()decodeSampledBitmapFromResource()进行缩放。

计算缩放图像所需的量,

if (imageHeight > reqHeight || imageWidth > reqWidth) {
      if (imageWidth > imageHeight ) {
          inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
      } else {
          inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
      }
    }
int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight);

您可以设置inSampleSize

 options.inSampleSize = inSampleSize;

然后最后确保你打电话,

options.inJustDecodeBounds = false;

否则它会将位图返回为null

  • 处理UI线程中的位图

    在UI线程上处理位图永远不会安全,因此在后台线程中执行该操作总是更好,并在完成该过程后更新UI。

  • 缓存位图

    API 12中提供了

    LruCache,但如果您对使用以下版本感兴趣,也可以在Support Library中找到它。因此,应该使用它来高效地完成图像的缓存。此外,您可以将DiskLruCache用于您想要的图像,然后在外部存储中保留更长时间。

  • 清除缓存

    有时当图像尺寸太大时,即使缓存图像也会导致OutOfMemoryError,因此在这种情况下,当图像超出示波器或不使用更长时间时更好地清除缓存,以便其他图像可以被缓存。

    我为此创建了一个演示示例,您可以从here下载

答案 1 :(得分:3)

您的案例符合预期。在Honeycomb之前,recycle()无条件地释放了记忆。但是在3.0及以上版本中,位图是正常垃圾收集内存的一部分。您在设备上有足够的RAM,您允许JVM分配超过58M的限制,现在垃圾收集器已满足,并且没有动力回收您的位图占用的内存。

您可以通过在具有受控RAM数量的仿真器上运行来验证这一点,或者在您的设备上加载一些消耗内存的服务 - GC将跳转到正常工作。您可以use DDMS进一步调查您的内存使用情况。

您可以尝试一些位图内存管理解决方案:Bitmaps in Android Bitmap memory leaks http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/,但请从official Android bitmap tips开始,如@Lalit Poptani所述{ {3}}

请注意,将位图移动到OpenGL内存作为纹理具有一些性能影响(但如果您最终将通过OpenGL渲染这些位图,则非常完美)。纹理和malloc解决方案都要求您明确释放不再使用的位图内存。

答案 2 :(得分:2)

绝对@Lalit Poptani的答案是这样做的方法,如果它们非常大,你应该真正扩展你的Bitmaps。如果可行的话,最好的方法是server-side,因为您还会缩短NetworkOperation时间。

关于MemoryCacheDiskCache的实施,这是最好的方法,但我仍然建议使用现有的库,它正是这样做的(Ignition并且你会节省很多时间,也会浪费很多内存,因为Heap GC memory leaks之后{{1}}没有被清空,我可以假设你可能有一些{{1}}太

答案 3 :(得分:1)

为了解决你的困境,我相信这是预期的行为。

如果你想释放内存,偶尔可以调用System.gc(),但实际上你应该让它自己管理垃圾收集。

我建议你保留一些简单的缓存(url / filename到bitmap),通过计算每个Bitmap占用的字节数来跟踪自己的内存使用情况。

/**
 * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
 * @param width
 * @param height
 * @param config
 * @return
 */
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
    long pixels=width*height;
    switch(config){
    case ALPHA_8: // 1 byte per pixel
        return pixels;
    case ARGB_4444: // 2 bytes per pixel, but depreciated
        return pixels*2;
    case ARGB_8888: // 4 bytes per pixel
        return pixels*4;
    case RGB_565: // 2 bytes per pixel
        return pixels*2;
    default:
        return pixels;
    }
}

然后你查询应用程序使用了多少内存以及可用的内存量,可能需要花费一半的时间并尝试将总图像缓存大小保持在此范围内,只需从列表中删除(解除引用)旧图像即可正在抵制这个限制,不回收。当垃圾收集器从缓存中取消并且未被任何视图使用时,让垃圾收集器清理位图。

/**
 * Calculates and adjusts the cache size based on amount of memory available and average file size
 * @return
 */
synchronized private int calculateCacheSize(){
    if(this.cachedBitmaps.size()>0){
        long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory 
        long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory);
        long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size();
        this.bitmapCacheSize = (int) (maxAllocation/avgSize);
    }
    return this.bitmapCacheSize;
}

我建议你不要使用recycle(),它会导致很多间歇性异常(例如看似最终的视图尝试访问回收的位图)并且通常看起来有些错误。

答案 4 :(得分:1)

在Android上处理位图时必须非常小心。让我重新说一下:即使在具有4 GB RAM的系统上,您也必须注意处理位图。这些家伙有多大,你有很多吗?如果它很大,你可能需要切碎并将其拼接起来。请记住,您使用视频RAM,这是一种与系统RAM不同的动物。

Pre-Honeycomb,Bitmaps在C ++层分配,因此RAM的使用对Java是不可见的,垃圾收集器无法访问。具有RGB24色彩空间的3 MP未压缩位图使用大约9-10兆字节(大约2048x1512)。因此,较大的图像可以很容易地填满你的堆。还要记住,无论用于视频RAM(有时是专用RAM,有时与系统共享),数据通常都是未压缩存储的。

基本上,如果您的目标是预蜂窝设备,您几乎必须像管理C ++程序一样管理Bitmap对象。运行位图recycle()onDestory()通常在图像不多的情况下有效,但如果屏幕上有大量图像,则可能需要即时处理它们。此外,如果您启动另一个活动,您可能必须考虑将逻辑放入onPause()和onResume()。

如果图像不在视频RAM中,您也可以使用Android文件系统或SQLite缓存图像。如果您使用像.jpg或带有大量重复数据的.png格式/

,您可以在RAM中缓存它。