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 p_w_picpathView=(ImageView) findViewById(R.id.p_w_picpath);
Bitmap bitmap=BitmapFactory.decodeStream....
p_w_picpathView.setImageBitmap(bitmap);
if(bitmap!=null)
bitmap.recycle();
造成异常的原因是系统在调用p_w_picpathView.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(p_w_picpathUrl);
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(p_w_picpathUrl, drawable);
savePic(bitmap, p_w_picpathUrl);// 保存图片
}
} 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异常意味着程序没有问题了么,其实不是的,如果一个应用程序占用了大量的内存,会导致这个应用程序运行缓慢,而且很卡,所以解决内存问题对于提高程序的性能尤为重要,所以在项目中,大家要做好内存的优化,开发高性能的应用程序。
方法之一:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
options.inPurgeable = true;// 允许可清除
options.inInputShareable = true;// 以上options的两个属性必须联合使用才会有效果
String sname = String.format( “xxx.png”, sTowerStyle, j, sDirction, i);
InputStream is = am.open(sname);
arrBmp[ iBmpIndex] = BitmapFactory .decodeStream(is, null, options);
方法之二:
将图片转化为缩略图再加载:
new
options.inSampleSize = 2;
Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png", options);
该段代码便是读取1.png的缩略图,长度、宽度都只有原图片的1/2。图片大小削减,占用的内存天然也变小了。这么做的弊病是图片质量变差,inSampleSize的值越大,图片的质量就越差。因为各手机厂商缩放图片的算法不合,在不合手机上的缩放图片质量可能会不合。笔者就遭受过moto手机上图片缩放后质量可以接管,三星手机上同样的缩放比例,质量却差很多的景象。
方法之三:
用ARBG_4444色彩模式加载图片:
Android中有四种,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存
RGB_565:每个像素占用2byte内存
Android默认的色彩模式为ARGB_8888,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。
new
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png", options);
以上代码便是将1.png以ARGB_4444模式读出。内存削减固然不如第一种办法明显,然则对于大多半图片,看不出与ARGB_8888模式有什么差别。不过在读取有渐变结果的图片时,可能有色彩条呈现。别的,会影响图片的殊效处理惩罚。
方法之四:
调用图片的recycle()办法:
这个其实不是真正降落图片内存的办法。首要目标是标识表记标帜图片对象,便利收受接管图片对象的本地数据。图片对象的本地数据占用的内存最大,并且与法度Java项目组的内存是分隔策画的。所以经常呈现Java heap足够应用,而图片产生OutOfMemoryError的景象。在图片不应用时调用该办法,可以有效降落图片本地数据的峰值,从而削减OutOfMemoryError的概率。不过调用了recycle()的图片对象处于“放弃”状况,调用时会造成法度错误。所以在无法包管该图片对象绝对不会被再次调用的景象下,不建议应用该办法。希罕要重视已经用setImageBitmap(Bitmap img)办法分派给控件的图片对象,可能会被体系类库调用,造成法度错误。
方法之五:
应用Matrix对象放大的图片如何更改色彩模式:
固然应用Matrix对象放大图片,必然会花费更多的内存,但有时辰也不得不如许做。放大后的图片应用的ARGB_8888色彩模式,就算原图片是ARGB_4444色彩模式也一样,并且没有办法在放大时直接指定色彩模式。可以采取以下办法更改图片色彩模式。
new
float newWidth = 200;//图片放大后的宽度
float newHeight = 300;//图片放大后的长度
matrix.postScale(newWidth / img.getWidth(), newHeight/ img.getHeight());
Bitmap img1 = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);//获得放大的图片
img2 = img1.copy(Bitmap.Config.ARGB_4444, false);//获得ARGB_4444色彩模式的图片
img = null;
img1 = null;
这里比起本来的图片额外生成了一个图片对象img1。然则体系会主动收受接管img1,所以实际内存还是削减了。
注:
尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.
因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用 JNI >> nativeDecodeAsset() 来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间.
如果在读取时加上图片的Config参数,可以更有效减少加载的内存,从而有效阻止抛出out of Memory异常另外,decodeStream直接拿的图片来读取字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了.
BitmapFactory.Options.inPurgeable;
如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间在系统内存不足时可以被回收,在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel),系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组.为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据.在inPurgeable为false时表示创建的Bitmap的Pixel内存空间不能被回收,这样BitmapFactory在不停decodeByteArray创建新的Bitmap对象,不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同,200个bitmap足以使大部分的设备重新OutOfMemory错误.当isPurgable设为true时,系统中内存不足时,可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误.
转载于:https://blog.51cto.com/beginnerjyh/1340667