10

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

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

我怎样才能避免这种情况?或者有效地回收位图?

编辑:实际上,我OutOfMemoryError在设备上分配超过 390Mb 的内存时给出了一个。读取 GC_* 日志显示只有 GC_FOR_ALLOC 有时释放了 3.8Mb,但几乎没有其他 GC 运行释放了一些东西。

4

5 回答 5

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。

  • 缓存位图

    LruCache可从 API 12 获得,但如果您对使用以下版本感兴趣,它也可在支持库中获得。因此,应该使用它有效地完成图像的缓存。您还可以将DiskLruCache用于您希望在外部存储中保留更长时间的图像。

  • 清除缓存

    有时,当您的图像尺寸太大时,甚至缓存图像也会导致OutOfMemoryError,因此在这种情况下,当您的图像超出范围或长时间未使用时,最好清除缓存,以便可以缓存其他图像。

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

于 2012-10-10T12:13:19.323 回答
3

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

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

位图内存管理可以尝试一些解决方案:Android Bitmaps in Android Bitmap memory leaks http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/,不过还是从官方开始Android 位图提示,如@Lalit Poptani详细回答中所述。

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

于 2012-10-14T06:25:59.940 回答
2

绝对@Lalit Poptani 答案是这样做的方法,Bitmaps如果它们非常大,你应该真正扩展你的。最好的方法是在server-side可能的情况下这样做,因为您也会减少NetworkOperation时间。

关于实现 aMemoryCache和thisDiskCache是最好的方法,但我仍然建议使用现有的库,它正是这样做的(Ignition,因为在我可以假设你可能也有一些Heap之后,你没有被清空。GCmemory leaks

于 2012-10-16T14:41:03.837 回答
1

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

如果您想释放内存,您可以偶尔调用System.gc(),但实际上您应该在大多数情况下让它管理垃圾收集本身。

我建议您保留某种简单的缓存(URL/文件名到位图),通过计算每个位图占用的字节数来跟踪自己的内存使用情况。

/**
 * 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(),它会导致很多间歇性异常(例如当看似已完成的视图尝试访问回收的位图时)并且通常看起来有问题。

于 2012-10-15T02:41:53.567 回答
1

在 Android 上处理位图时必须非常小心。让我换个说法:即使在具有 4 GB RAM 的系统上,您也必须小心处理位图。这些家伙有多大,你有很多吗?如果它很大,您可能需要将其切碎并平铺。请记住,您使用的是视频 RAM,它与系统 RAM 不同。

在Honeycomb 之前,Bitmap 被分配在C++ 层,因此RAM 的使用对Java 是不可见的,并且垃圾收集器无法访问。具有 RGB24 颜色空间的 3 MP 未压缩位图使用大约 9-10 兆字节(大约 2048x1512)。因此,较大的图像可以轻松填满您的堆。还要记住,在任何用于视频 RAM(有时是专用 RAM,有时与系统共享)中,数据通常以未压缩的形式存储。

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

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

于 2012-10-16T12:37:43.297 回答