android 解决bitmap造成的OOM问题

前一段时间由于项目中图片过多经常造成内存溢出的问题,现在已经解决。

1通过强引用和弱引用以及LRU算法。

private static final int HARD_CACHE_CAPACITY = 20;//强引用的bitmap的数量

//为了提高图片的利用率,通过单链表实现先进先出,将老的图片移到软引用里面保存

private static LinkedHashMap<String, Drawable> sHardBitmapCache = new LinkedHashMap<String, Drawable>(
   HARD_CACHE_CAPACITY / 2, 0.75f, true) {
  protected boolean removeEldestEntry(Map.Entry<String, Drawable> eldest) {
   if (size() > HARD_CACHE_CAPACITY) {
    // Entries push-out of hard reference cache are transferred to
    // soft reference cache
    sSoftBitmapCache.put(eldest.getKey(),
      new WeakReference<Drawable>(eldest.getValue()));
    return true;
   } else
    return false;

  };
 };

//通过软引用保存部分图片

 private static LRULinkedHashMap<String, WeakReference<Drawable>> sSoftBitmapCache = new LRULinkedHashMap<String, WeakReference<Drawable>>(
   20);

 

// 实现LRU算法,将bitmap从软引用中移出
 public static class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
  private final int maxCapacity;

  private static final float DEFAULT_LOAD_FACTOR = 0.75f;

  private final Lock lock = new ReentrantLock();

  public LRULinkedHashMap(int maxCapacity) {
   super(maxCapacity, DEFAULT_LOAD_FACTOR, true);
   this.maxCapacity = maxCapacity;
  }

  @Override
  protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
   return size() > maxCapacity;
  }

  @Override
  public boolean containsKey(Object key) {
   try {
    lock.lock();
    return super.containsKey(key);
   } finally {
    lock.unlock();
   }
  }

  @Override
  public V get(Object key) {
   try {
    lock.lock();
    return super.get(key);
   } finally {
    lock.unlock();
   }
  }

  @Override
  public V put(K key, V value) {
   try {
    lock.lock();
    return super.put(key, value);
   } finally {
    lock.unlock();
   }
  }

  public int size() {
   try {
    lock.lock();
    return super.size();
   } finally {
    lock.unlock();
   }
  }

  public void clear() {
   try {
    lock.lock();
    super.clear();
   } finally {
    lock.unlock();
   }
  }

  public Collection<Map.Entry<K, V>> getAll() {
   try {
    lock.lock();
    return new ArrayList<Map.Entry<K, V>>(super.entrySet());
   } finally {
    lock.unlock();
   }
  }
 }

2通过压缩图片,可以有效的減少bitmap的大小,但是图片清晰度要求高的项目中此方法不可取,另外在项目中,如果是从网络上取图片,不建议这么做,浪费了很大的流量,而且还导致图片不清晰,应该将图片的参数传给服务器端,服务器端根据参数来压缩图片,这样可以节约流量。如果是拍照,建议调用下面的方法,因为之前在项目中遇到拍照,由于有些手机内存小,而且图片比较高清,decode的时候,图片过大导致内存溢出。

 InputStream is = this.getResources().openRawResource(R.drawable.pic);
     BitmapFactory.Options options=new BitmapFactory.Options();
     options.inJustDecodeBounds = false;
     options.inSampleSize = 2;   //width,hight设为原来的四分之一
     Bitmap btp =BitmapFactory.decodeStream(is,null,options);

3通过调用Bitmap的recycle 方法

   这个方法只是做一个标记,告诉Java虚拟机,这个图片可以回收了,bitmap图片的回收还是要根据GC的时机

   这个方法有一个弊端就是容易造成trying to use a  recycled bitmap.的异常

   ImageView imageView=(ImageView) findViewById(R.id.image);

   Bitmap bitmap=BitmapFactory.decodeStream....

    imageView.setImageBitmap(bitmap);

    if(bitmap!=null)

     bitmap.recycle();

造成异常的原因是系统在调用imageView.setImageBitmap(bitmap)的时候,回根据bitmap这个引用去找在内存中的图片,结果发现图片已经回收了,就回报这个异常。

所以, 怎样才可以保证不会早了呢?

关于图片显示,重要的时间点:

设置进去的时间点;

 画面画出来的时间点;

最保险最笨的做法,在新的图片设置进去以后再recycle掉老的图片,这样做的坏处在于,在某个时间段,你需要的空间是double的【新旧两套都在】;

如果你不偏向于那么做,又有时间,可以考虑后面一个时间点,除了setImage以及其它代码中显示调用那个bitmap的时候我们会检查bitmap,在acticvity变为visible的时候系统还是会去找之前设置进去的bitmap【即使你的onResume方法里面并没有提到去refresh UI,这件事情它也是会去做的,大概不然它就不知道这次该显示些什么了】。所以,在UI线程里面,在一个不可能被打断的方法里面,是先设置新的bitmap还是先recycle旧的图片是没有影响的。

譬如说 mBitmap.recycle();

mBitmap = ….. //设置

mImageView.setImage(mBitmap);

这样的代码是完全可以的。

 你先调用ImageView.setImageBitmap(null)

然后再调用if(bitmap!=null)

     bitmap.recycle();

说白了要先找到源,处理了,然后再去回收。

  如果有一张图片被多个Activity以用,而且是通过HashMap引用的,如果你要执行recycle

  这个时候为了保险起见

       Bitmap bitmap=map.remove(url)

       map.put(url,null);

      if(bitmap!=null){

       bitmap.recycle();

}

map.remove(url);

最重要的就是确保:在UI线程【因为设置UI显示只能在UI主线程里】里面一个不可能被打断的方法里面。这个是为了确保在两者之间UI主线程不可能被打断,不可能刚好从invisible变成visible。

所以,特别小心两种东西:

1. 多线程【个人觉得最好不要在其他线程里面调用UI用过的bitmap的recycle方法,多线程之间是很难保证时间顺序的,暂时没有想出一种在background thread里面recycle的合理的方式】;

2. 非及时发生的方法:譬如,发intent啊,发notify啊去通知UI主线程去做UI重新刷新并不能替代mImageView.setImage(mBitmap);这样的句子。完全有可能,你确实发了intent出去了,但是目标activity之一还没有做UI重新设置【Q: maybe没收到 or 收到但还是等待处理,不确定这两种可能是不是都有可能】,这个时候这个acitivity变成visible了,系统仍然试图找旧的图片,找不到了就会报exception了。


4对于listView这样的控件应该加上缓存机制

例如

final Viewhodler viewhodler;

   if (convertView == null) {
    viewhodler = new Viewhodler();
    LayoutInflater inflater = ((Activity) context)
      .getLayoutInflater();
    convertView = inflater.inflate(R.layout.film_item, null);

    viewhodler.icon = (ImageView) convertView
      .findViewById(R.id.icon);
    viewhodler.value = (TextView) convertView
      .findViewById(R.id.price);
      convertView.setTag(viewhodler);

   } else {
    viewhodler = (Viewhodler) convertView.getTag();
   }

 

 

5在一个Activity中如果有一个listView或者gallery加载图片,退出Activity,这些线程仍旧在执行,而且大量内存被占用,而且更加容易造成oom,这个我在之前的项目中经常遇到这样的问题,后来我通过线程池+FutureTask解决了。在退出Activity的时候关闭在这个Activity里面开的线程,可以让Activity尽快结束,以便Java垃圾回收机制能够回收。

 如果通过线程池+Runnable实现的图片加载,是没法停止单个的线程的。但是可以通过线程池+FutureTask来实现。

下面先介绍一下FuturTask

 

FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。

Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

FutureTask有下面几个重要的方法:

1.get()

阻塞一直等待执行完成拿到结果

 

2.get(int timeout, TimeUnit timeUnit)

阻塞一直等待执行完成拿到结果,如果在超时时间内,没有拿到抛出异常

 

3.isCancelled()

是否被取消

 

4.isDone()

是否已经完成

 

5.cancel(boolean mayInterruptIfRunning)

试图取消正在执行的任务

我们正是要通过cancel方法来取消一个线程,在退出Activity的时候调用FutureTask的cancel(true)方法

 

6通过以上的方法,就不会有内存溢出了么,答案是否定的,因为由于Java垃圾回收机制的不确定性,可能在某一个时刻,内存占用较大,而且这个时候还在不断的加载图片,这个时候就有可能发生内存溢出的情况。所以在代码中要对发生内存溢出进行处理。

而且这个异常也要捕获。以下是截取的部分代码片段

try {
    URL url = new URL(imageUrl);
    conn = (HttpURLConnection) url.openConnection();
    conn.setConnectTimeout(10 * 1000);
    conn.connect();
    inputStream = conn.getInputStream();
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    // options.inSampleSize = 2;
    bitmap = BitmapFactory.decodeStream(inputStream, null, options);
    if (bitmap != null) {
     drawable = new BitmapDrawable(context.getResources(),
       bitmap);
     addBitmapToCache(imageUrl, drawable);
     savePic(bitmap, imageUrl);// 保存图片
    }
   } catch (Exception e) {
    return null;
   } catch (OutOfMemoryError oom) {
    clearCache();
    System.gc();
     } finally {
    if (inputStream != null) {
     try {
      inputStream.close();
     } catch (Exception e) {

     }
    }
    if (conn != null) {
     try {
      conn.disconnect();
     } catch (Exception e) {

     }
    }

   }

7程序没有发生OOM异常意味着程序没有问题了么,其实不是的,如果一个应用程序占用了大量的内存,会导致这个应用程序运行缓慢,而且很卡,所以解决内存问题对于提高程序的性能尤为重要,所以在项目中,大家要做好内存的优化,开发高性能的应用程序。

 

 

 

      

    

 

    

 

 

 

 

 

 

   

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值