前一段时间由于项目中图片过多经常造成内存溢出的问题,现在已经解决。
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异常意味着程序没有问题了么,其实不是的,如果一个应用程序占用了大量的内存,会导致这个应用程序运行缓慢,而且很卡,所以解决内存问题对于提高程序的性能尤为重要,所以在项目中,大家要做好内存的优化,开发高性能的应用程序。