android应用尤其是涉及到很多图片处理的经常会遇到OOM(Out Of Memory),为什么会导致OOM,又该如何解决呢?
OOM原因分析:
android每一个应用都有一个独立的进程,每个进程都是实例化了dalvik虚拟机实例的linux进程。Dalvik 主要管理的内存有 Java heap 和 native heap 两大块。Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),而native heap大小则是不受次限制的。
当我们需要显示大的bitmap对象或者较多的bitmap的时候,如果使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置图片,则容易出现OOM,这个是因为bitpmap分配内存受限于java heap大小(一张在1024*1024图片,假设照片是用ARGB_8888格式,那么需要占用4M的内存存储像素点信息, bitmap分辨率越高,所占用的内存就越大,这个是以2为指数级增长的。)。此处引出一个问题:bitmap对象是分配在native heap还是java heap上的呢?后续会在单独一篇文章中讲解(Bitmap分配在java heap还是native heap)
解决图片引起的OOM思路
(1)大的图片占用内存会非常大,但是在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响,可以给ImageView设置比原图要小的合理尺寸的bitmap。(具体的实现方式可以在阅读完其他部分再细读)
查看BitmapFactory接口会发现,多个接口要求传入Options对象,options支持设置的参数如上图,其中有两个比较重要的参数:inJustDecodeBounds和inSampleSize
/**
* If set to true, the decoder will return null (no bitmap), but
* the out... fields will still be set, allowing the caller to query
* the bitmap without having to allocate the memory for its pixels.
*/
public boolean inJustDecodeBounds;
/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
* decoder will try to fulfill this request, but the resulting bitmap
* may have different dimensions that precisely what has been requested.
* Also, powers of 2 are often faster/easier for the decoder to honor.
*/
public int inSampleSize;
inJustDecodeBounds参数可以让我们在不对图片解码的情况下直接获取到图片的大小,非常方便高效;
inSampleSize参数则是用于设置生成的bitmap的大小,大于1时要求生成的bimap只有原图1/inJustDecodeBounds大小,建议使用2、4、8等2指数值,对于解码将容易和简单。
(2)如果一个ScrollView中有很多图片,即使使用上述方法,内存中也会有很多bitmap对象,依然可能会发生OOM。但是Scrollview中的图片只是在显示的时候才需要加载,可以通过缓存方式将这些已经加载的bitmap缓存起来,当有新的bitmap需要加载时且超过缓存总容量时将暂时不使用的bimap回收,控制内存占用,防止OOM。
现有的图片加载开源框架
BitmapUtils主要解决Android加载图片出现的OOM问题,采取了多级缓存机制(内存缓存和磁盘缓存)保存图片避免OOM现象,采取异步加载bitmap,在listView快速滑动时停止加载。
BitmapUtils接口支持Assets、Url、Path的图片路径。
BitmapGlobalConfig主要配置磁盘缓存路径,程序内存缓存大小。(不设置使用默认值),直接在bitmapUtils初始化时设置。
BitmapDisplayConfig主要配置异步加载未完成时临时显示的图片,加载失败显示的图片,加载过程中的动画,图片是否翻转,是否显示原图以及显示图片的最大Size等。BitmapUtils支持内存缓存(LruCache)和磁盘缓存(LruDiskCache)
内存缓存主要采取LinkedHashMap< MemoryCacheKey, Bitmap >存储bitmap到缓存中,默认系统缓存为4M,当内存超出4M,移除上一元素的缓存。磁盘缓存的目的是xxxBitmapUtils加载Bitmap策略
(1) 首先加载内存缓存bitmap。
(2) 在内存缓存获取不到的情况下,加载文件缓存的文件输入流,把文件输入流转化为bitmap,同时存储到内存缓存中,并且回收当前的bitmap。
(3) 直接加载路径下的资源(Assets、Path、URL)处理都一样,都是获取文件输入流。通过BitmapFactory.decodeFileDescriptor,把文件输入流转化为bitmap,同时保存文件输入流到文件缓存中,保存bitmap到内存缓存中,回收当前的bitmap。