您的位置:首页 > Web前端

Bitmap内存优化--使用BitmapFactory.options及SoftReference解决OutOfMemory问题

2015-06-30 09:02 573 查看
加载Bitmap经常出现OutOfMemory原因

    Android手机分配给App的内存并不是无限的(即最大可用内存),而是有固定限制的,具体到每个机型,可分配的最大内存不同,但某些机型最大可使用内存可能仅16M,因此App运行必须考虑内存溢出问题。

    Bitmap会非常占用内存,一个2592x1936 pixels (5 MB)的图片,bitmap的配置是使用ARGB_8888,,那么加载这张照片到内存会大概需要19MB(2592*1936*4 bytes) 的内存, 如果在只有16MB上限的设备上运行的话,会迅速消耗掉设备的整个内存,导致程序崩溃。

    要处理Bitmap内存占用过大,有三个优化策略(本文主要介绍前两个):

    策略一:对bitmap进行缩放,直接降低在内存中的存储空间;

    策略二:通过SoftReferences(软引用)策略。

    策略三:开启线程进行异步加载图片,避免出现卡顿现象

    策略一:利用BitmapFactory. Options缩放

    对Bitmap进行缩放,主要是利用BitmapFactory.Options,通过对Options中参数进行设置,可以实现对Bitmap的定制缩放。Options几个重要参数如下:

<span style="font-family:KaiTi_GB2312;font-size:18px;">//利用BitmapFactory从files, streams, byte-arrays中创建Bitmap对象
public class BitmapFactory {
public static class Options {
//如果设置inJustDecodeBounds =true, decode不会返回Bitmap而是返回null,但会获得图片的真实Width,Height并设置到Options中,此方法不会增加内存负担。
public boolean inJustDecodeBounds;

// 设置inSampleSize对原始图片进行缩放以节省内存,例如inSampleSize=4,则width/height均缩放到1/4,像素点缩放到1/16。如果inSampleSize<1,则自动设置inSampleSize=1;inSampleSize都是2的幂数,inSampleSize自动设置到最靠近的幂数,如inSampleSize=5,则自动设置inSampleSize=4
public int inSampleSize;

//bitmap的宽度,outWidth与inJustDecodeBounds参数无关,如果bitmap无法解析,outWidth=-1
public int outWidth;
//bitmap的高度,outHeight与inJustDecodeBounds参数无关,如果bitmap无法解析,outHeight =-1
public int outHeight;
}
</span>


一个简单的图片缩放Demo,将图片缩放到指定宽高即Limit_Width、Limit_Height
<span style="font-family:KaiTi_GB2312;font-size:18px;">public Bitmap getBitmap(String path){
BitmapFactory.Options options = new Options();

//设置为true,获得图片原始宽高
options.inJustDecodeBounds=true;
Bitmap bitmap =BitmapFactory.decodeFile(path,options);

//设置缩放倍数Limit_Height,有很多种方法以具体情况而定,这里只是简单地平均
options.inSampleSize=(options.outWidth/Limit_Width+options.outHeight/Limit_Height)/2;//

options.inJustDecodeBounds=false;
bitmap=BitmapFactory.decodeFile(path,options);
return bitmap;
}</span>

策略二:SoftReferences(软引用)策略

    当应用从网络中获取网络图片数据时,用户完全有可能做一些重复性的操作去查看相同的图片信息。对于这样的问题,通常会有两种解决方法: 一种是把过去查看过的图片信息保存在内存中,每一个存储了图片信息的 Java 对象的生命周期都贯穿整个应用程序生命周期,另一种是当用户开始查看其他图片信息的时候,把存储了当前的图片信息的 Java 对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该图片信息的时候,重新获取图片信息。

    很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含图片信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。像访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的 Java 对象的引用,必将减少不必要的访问,大大提高程序的运行速度。

    如何使用软引用

    SoftReference 的特点是它的一个实例保存着一个 Java 对象的软引用,该软引用的存在不妨碍垃圾收集器线程对该 Java 对象的回收。也就是说,一旦SoftReference 保存着一个 Java 对象的软引用之后,在垃圾收集器线程对这个 Java 对象回收之前, SoftReference 类所提供的 get() 方法都会返回 这个Java
对象的强引用。另外,一旦垃圾线程回收该 Java 对象之后, get() 方法将返回 null 。软引用的使用方法如下面的Java代码所示 :

<span style="font-family:KaiTi_GB2312;font-size:18px;">    MyObject aRef  =  new   MyObject();//创建一个对象
SoftReference aSoftRef = new SoftReference( aRef );//创建对象的软引用</span>


    上面的代码执行后,对于MyObject 对象,有两个引用路径,一个是来自 aSoftRef对象的软引用,一个来自变量 aRef 的强引用,所以 MyObject对象是强可及对象。紧跟着,可以使用下面的java的代码结束 aReference 对 MyObject 实例的强引用 :

<span style="font-family:KaiTi_GB2312;font-size:18px;">    aRef = null ;//断开对象的强引用</span>


    此后, MyObject 对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个 SoftReference 对该对象的引用而始终保留该对象。 Java 虚拟机的垃圾收集线程对软可及对象和其他一般 Java 对象进行了区别对待 ,软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出 OutOfMemoryError 之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可及对象会被虚拟机尽可能保留。如果想获取软引用中包含的对象,可以使用下面的Java代码:

MyObject anotherRef =(MyObject) aSoftRef .get();//通过软引用获取对象


在回收这些对象之前,可以通过上面的代码重新获得对该实例的强引用。而回收之后,当调用软引用的get() 方法时,返回的是 null 。

   如何使用 ReferenceQueue

   作为一个 Java 对象, SoftReference 对象除了具有保存软引用的特殊性之外,也具有 Java 对象的一般性。所以,当软可及对象被回收之后,虽然这个 SoftReference 对象的 get() 方法返回 null, 但这个 SoftReference 对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量 SoftReference 对象带来的内存泄漏。在 java.lang.ref 包里还提供了 ReferenceQueue 。如果在创建 SoftReference 对象的时候,使用了带有一个
ReferenceQueue 对象作为参数的构造方法,如下面的Java代码 :

ReferenceQueue queue = new ReferenceQueue();//创建引用队列
SoftReference  ref = new SoftReference( aMyObject, queue );// 把引用加入到引用队列


    当这个 SoftReference 所软引用的 aMyOhject 被垃圾收集器回收的同时,ref 所强引用的 SoftReference 对象被列入 ReferenceQueue 。也就是说, ReferenceQueue 中保存的对象是 Reference 对象,而且是已经失去了它所软引用的对象的 Reference 对象。另外从 ReferenceQueue 这个名字也可以看出,它是一个队列,当调用它的 poll() 方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个 Reference
对象。
    在任何时候,都可以调用 ReferenceQueue 的 poll() 方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个 null, 否则该方法返回队列中最前面一个 Reference 对象。利用这个方法,可以检查哪个 SoftReference 所软引用的对象已经被回收。可以把这些失去所软引用的对象的 SoftReference 对象清除掉,如下面的Java代码所示。:

SoftReference ref = null ;
while ((ref = (EmployeeRef) q .poll()) != null ) {
// 清除 ref
}


[b]   SoftReferences(软引用)源码体系(如下图):


   



   Reference抽象基类

   APi中描述如下:Reference是描述对Object对象引用行为的抽象类,Reference是与系统的garbage collector(内存垃圾回收机制)密切配合的。有三种不同形式的引用方式,分别是SoftReference(软引用),WeakReference(弱引用),PhantomReference,这三个引用对garbage collector的限制是逐渐降低的,SoftReference的引用对象,只有在可能出现OutOfMemory问题时才会被系统回收,

WeakReference的引用对象可能随时被回收。这三种方式引用的对象都是在系统中创建的强引用,即通过new创建出来的对象,这些对象在方法域范围内不可能被系统回收,但一旦过了生命周期,就会被丢弃,通过软引用,可以适当延迟被回收。

public abstract class Reference<T> {

Reference() {
}
Reference(T r, ReferenceQueue<? super T> q) {
referent = r;
queue = q;
}

//设置referent为null,并不需要将此Referent加入到队列enqueue
public void clear() {
referent = null;
}
//将referent对象加入到队列
public boolean enqueue() {
return enqueueInternal();
}
//返回软引用指向的引用对象T,如果对象T被回收,则返回null
public T get() {
return getReferent();
}
//判断reference是否加入队列
public boolean isEnqueued() {
return queueNext != null;
}


    SoftReference类

    APi中描述如下:避免使用软引用构建Cashing(高速缓存)!在实际操作中,软引用构建的缓存是低效的,因为系统无法合理判断什么时候应该清除软引用,什么时候应该保留软引用,如果过早清除Reference,Reference行为显得多此一举,如果过晚清除又会增加内存开销。应用应该使用android.util.LruCache而非Reference优化内存。如果garbage collector将要回收Reference,其行为是:1)回收Reference指向的Object,避免内存崩溃;2)清除Reference,并将Reference加入到队列,但这个动作可能会延迟。

public class SoftReference<T> extends Reference<T> {
//创建一个指向T的软引用,新建的软引用不在软引用队列中注册。
public SoftReference(T r) {
super(r, null);
}
//创建一个指向T的软引用,并将其注册到软引用队列
public SoftReference(T r, ReferenceQueue<? super T> q) {
super(r, q);
}


指定路径下图片Bitmap的SoftRefence引用,一个简单地Demo如下:

public SoftReference<Bitmap> getSoftBitmap(String path){
BitmapFactory.Options options = new Options();

//设置为true,获得图片原始宽高
options.inJustDecodeBounds=true;
Bitmap bitmap =BitmapFactory.decodeFile(path,options);

//设置缩放倍数Limit_Height,有很多种方法以具体情况而定,这里只是简单地平均
options.inSampleSize=(options.outWidth/Limit_Width+options.outHeight/Limit_Height)/2;//

options.inJustDecodeBounds=false;

bitmap=BitmapFactory.decodeFile(path,options);

SoftReference<Bitmap> softBitmap=new SoftReference<Bitmap>(bitmap);

return softBitmap;
}

本文部分内容参考:http://blog.csdn.net/hbzh2008/article/details/9038029
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息