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、通过压缩图片,可以有效的�p少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异常意味着程序没有问题了么,其实不是的,如果一个应用程序占用了大量的内存,会导致这个应用程序运行缓慢,而且很卡,所以解决内存问题对于提高程序的性能尤为重要,所以在项目中,大家要做好内存的优化,开发高性能的应用程序。



方法之一:

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);


方法之二:

将图片转化为缩略图再加载: 
BitmapFactory.Options options = new BitmapFactory.Options();

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,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。

BitmapFactory.Options options = new BitmapFactory.Options();

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色彩模式也一样,并且没有办法在放大时直接指定色彩模式。可以采取以下办法更改图片色彩模式。

Matrix matrix = new Matrix();

float newWidth = 200;//图片放大后的宽度

float newHeight = 300;//图片放大后的长度

matrix.postScale(newWidth / img.getWidth(), newHeight/ img.getHeight());

Bitmap img1 = Bitmap.createBitmap(img, 00, 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 错误.


更多相关文章

  1. 关于android获得图片的总结
  2. Android(安卓)Camera内存问题剖析
  3. 【Android】Android中使用JNI调用底层C++代码
  4. 我把阿里、腾讯、字节跳动、美团等Android性能优化实战整合成了
  5. Android(安卓)Intent机制实例详解(1)
  6. Android(安卓)P 源码分析 5 - Low memory killer 之 lmkd 守护进
  7. Android、web中的图片和语音的加密
  8. Android(安卓)ListView 图片异步加载和图片内存缓存
  9. Android(安卓)缓存浅谈(一) LruCache

随机推荐

  1. Android(安卓)8.0 常见bug
  2. @功能 中用到 android EditText插入字符
  3. android ndk开发环境搭建(windows环境下)
  4. MTK android代码架构
  5. Eclipse开发Android报错android library
  6. 通过手势实现页面切换,关于Viewpaper介绍
  7. [2] Android进程孵化图
  8. Android(安卓)activity生命周期的几种场
  9. Android经常使用UI组件 - TextView
  10. onPrepareOptionsMenu 和onCreateOptions