在启用largeHeap
选项之前,我正在处理大型位图,它几乎消耗了应用程序可用的整个内存,并且通过导航回收它并加载新的内存几乎可以在完整的堆上运行。但是,当某些操作需要更多内存时,应用程序崩溃。所以我让largeHeap=true
有更多的内存。
但这样做有一个意想不到的行为,看起来recycle()
位图方法大部分时间都不起作用,而且应用程序在58Mb内存中工作(并且超过有时抛出OutOfMemoryException
)现在以指数方式消耗内存并且不断增长(现在测试我做了231Mb分配的内存),预期的行为是内存管理继续工作,应用程序不会使用超过60Mb。
我该如何避免?还是有效地回收位图?
编辑:实际上,我在设备上分配超过390Mb的内存时给它OutOfMemoryError
。
读取GC_ *日志显示只有GC_FOR_ALLOC有时会释放3.8Mb,但几乎从未在其他GC运行中释放过某些内容。
答案 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
时间。
关于MemoryCache
和DiskCache
的实施,这是最好的方法,但我仍然建议使用现有的库,它正是这样做的(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中缓存它。