初级时在ListView或是GridView中需要处理大量图片时,创建的一个单独类。ImageLoader类,来实现异步双缓存加载图片功能。
异步:AsyncTask来实现。
双缓存:一级缓存LinkedHashMap(LRU(Least Recently Used)策略,即当内存使用不足时,把最近最少使用的数据从缓存中移除,保留使用最频繁的数据。
二级缓存ConcurrentHashMap实现。
LinkedHashMap可以记住插入顺序,ConcurrentHashMap可以线程安全。
LinkedHashMap实现一级缓存
构造方法 new LinkedHashMap(10, 0.75, true);
参数1:缓存的大小一般为10或是5。
参数2:加载因子 经验值为0.75。
参数3:true:按照最近访问量的高低排序,false:按照插入顺序排序。
/**
* 一级缓存 mFirstLevelCache
* 0.75是加载因子为经验值
* true则表示按照最近访问量的高低排序,false则表示按照插入顺序排序
* */
private HashMap<String, Bitmap> mFirstLevelCache = new LinkedHashMap<String, Bitmap>(MAX_CAPACITY / 2, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > MAX_CAPACITY) {// 当超过一级缓存阈值的时候,将老的值从一级缓存搬到二级缓存
mSecondLevelCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else {
return false;
}
}
};
二级缓存ConcurrentHashMap
/**
* 二级缓存 mSecondLevelCache
* 采用的是软引用 只有在内存吃紧的时候软引用才会被回收 有效的避免了oom
* */
private ConcurrentHashMap<String, SoftReference<Bitmap>> mSecondLevelCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(MAX_CAPACITY / 2);
一级缓存放入到二级缓存中时
m.put(eldest.getKey(),new SoftReference<Bitmap>(eldest.getValue()));
总结
设置两级缓存,第一级用LinkedHashMap<String,Bitmap>保留Bitmap的强引用,但是控制缓存的大小MAX_CAPACITY=10,当继续向该缓存中存数据的时候。
将会把一级缓存中的最近最少使用的元素放入二级缓存ConcurrentHashMap<String, SoftReference<Bitmap>>,二级缓存中保留的Bitmap的软引用。 SoftReference:它保存的对象实例,除非JVM即将OutOfMemory,否则不会被GC回收。这个特性使得它特别适合设计对象Cache。对于Cache,我们希望被缓存的对象最好始终常驻内存,但是如果JVM内存吃紧,为了不发生OutOfMemoryError导致系统崩溃,必要的时候也允许JVM回收Cache的内存,待后续合适的时机再把数据重新Load到Cache中。这样可以系统设计得更具弹性。
工具类
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static final int MAX_CAPACITY = 10;// 一级缓存的最大空间
private static final long DELAY_BEFORE_PURGE = 10 * 1000;// 定时清理缓存
/**
* 一级缓存 mFirstLevelCache
* 0.75是加载因子为经验值
* true则表示按照最近访问量的高低排序,false则表示按照插入顺序排序
* */
private HashMap<String, Bitmap> mFirstLevelCache
= new LinkedHashMap<String, Bitmap>(MAX_CAPACITY / 2, 0.75f, true) {
private static final long serialVersionUID = 1L;
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > MAX_CAPACITY) {// 当超过一级缓存阈值的时候,将老的值从一级缓存搬到二级缓存
mSecondLevelCache.put(eldest.getKey(),
new SoftReference<Bitmap>(eldest.getValue()));
return true;
}
return false;
};
};
/**
* 二级缓存 mSecondLevelCache
* 采用的是软引用 只有在内存吃紧的时候软引用才会被回收 有效的避免了oom
* */
private ConcurrentHashMap<String, SoftReference<Bitmap>> mSecondLevelCache
= new ConcurrentHashMap<String, SoftReference<Bitmap>>(MAX_CAPACITY / 2);
//Handler对象
private Handler mPurgeHandler = new Handler();
/**
* 定时清理缓存
* */
private Runnable mClearCache = new Runnable() {
public void run() {
clear();
}
};
/**
* 重置缓存清理的timer
* */
private void resetPurgeTimer() {
mPurgeHandler.removeCallbacks(mClearCache);
mPurgeHandler.postDelayed(mClearCache, DELAY_BEFORE_PURGE);
}
/**
* 清理一级二级缓存的方法
*/
private void clear() {
mFirstLevelCache.clear();
mSecondLevelCache.clear();
}
/**
* 从缓存中拿图片,先从一级缓存中拿,判断后再从二级缓存中拿
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap = null;
bitmap=getFromFirstLevelCache(url);//从一级缓存中拿
if(bitmap!=null){
return bitmap;
}else{
bitmap=getFromSecondLevelCache(url);//从二级缓存中拿
return bitmap;
}
}
/**
* 从二级缓存中拿图片方法
*/
private Bitmap getFromSecondLevelCache(String url) {
Bitmap bitmap = null;
SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference!=null) {
bitmap=softReference.get();
if (bitmap==null) {//由于内存吃紧 软引用已经被gc回收了
mSecondLevelCache.remove(url);
}
}
return bitmap;
}
/**
* 从一级缓存中拿图片方法
*/
private Bitmap getFromFirstLevelCache(String url) {
Bitmap bitmap = null;
synchronized (mFirstLevelCache) {
bitmap = mFirstLevelCache.get(url);
if (bitmap!=null) {// 将最近访问的元素放到链的头部,提高下一次访问该元素的检索速度(LRU算法)
mFirstLevelCache.remove(url);
mFirstLevelCache.put(url, bitmap);
}
}
return bitmap;
}
/**
* 菜谱页面ListView模式 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
*/
public void loadImageListView(String url, BaseAdapter adapter, ViewHolder holder){
resetPurgeTimer();//重置缓存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//从缓存中读取
if(bitmap==null){//缓存中没有相应的图片
//先设为默认图片
holder.menu_iv.setImageResource(R.drawable.default_s);
//再通过AsyncTask获取图片
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//缓存中有相应的图片
holder.menu_iv.setImageBitmap(bitmap);//设为缓存图片
}
}
/**
* 菜谱页面GridView模式 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
*/
public void loadImageGridView(String url, BaseAdapter adapter,com.mealo.adapter.CookBookMenuGridViewAdapter.ViewHolder holder) {
resetPurgeTimer();//重置缓存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//从缓存中读取
if(bitmap==null){//缓存中没有相应的图片
//先设为默认图片
holder.menu_iv.setImageResource(R.drawable.default_s);
//再通过AsyncTask获取图片
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//缓存中有相应的图片
holder.menu_iv.setImageBitmap(bitmap);//设为缓存图片
}
}
/**
* 菜谱页面ImageView模式 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
*/
public void loadImageImageView(String url,ImageView iv) {
resetPurgeTimer();//重置缓存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//从缓存中读取
if(bitmap==null){//缓存中没有相应的图片
//先设为默认图片
iv.setImageResource(R.drawable.default_b);
//再通过AsyncTask获取图片
ImageLoadTaskImageView imageLoadTask=new ImageLoadTaskImageView();
imageLoadTask.execute(url,iv);
}else{//缓存中有相应的图片
iv.setImageBitmap(bitmap);//设为缓存图片
}
}
/**
* 订单页面 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
*/
public void loadImageOrderListView(String url, BaseAdapter adapter,com.mealo.adapter.OrderDialogAdapter.ViewHolder holder) {
resetPurgeTimer();//重置缓存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//从缓存中读取
if(bitmap==null){//缓存中没有相应的图片
//先设为默认图片
holder.titleiv.setImageResource(R.drawable.default_s);
//再通过AsyncTask获取图片
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//缓存中有相应的图片
holder.titleiv.setImageBitmap(bitmap);//设为缓存图片
}
}
/**
* 菜品详情页面 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
*/
public void loadImageMenuDetailedListView(String url, BaseAdapter adapter,com.mealo.adapter.MenuDetailedDialogAdapter.ViewHolder holder) {
resetPurgeTimer();//重置缓存清理的timer
Bitmap bitmap=getBitmapFromCache(url);//从缓存中读取
if(bitmap==null){//缓存中没有相应的图片
//先设为默认图片
holder.titleiv.setImageResource(R.drawable.default_s);
//再通过AsyncTask获取图片
ImageLoadTask imageLoadTask=new ImageLoadTask();
imageLoadTask.execute(url, adapter, holder);
}else{//缓存中有相应的图片
holder.titleiv.setImageBitmap(bitmap);//设为缓存图片
}
}
/**
* 图片放入一级缓存方法
*/
public void addImage2Cache(String url, Bitmap value) {
if(value==null||url==null){
return;
}
synchronized (mFirstLevelCache) {
mFirstLevelCache.put(url, value);
}
}
/**
* ImageLoadTask(内部类)继承AsyncTask相当于新开了一个线程
* 本地加载sd卡,ListView模式和GridView模式
* */
class ImageLoadTask extends AsyncTask<Object, Void, Bitmap> {
String url;
BaseAdapter adapter;
@Override
protected Bitmap doInBackground(Object... params) {
url=(String)params[0];
adapter=(BaseAdapter)params[1];
Bitmap drawable=loadImageFromSd(url);
return drawable;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result==null) {
return;
}
addImage2Cache(url, result);//放入缓存
adapter.notifyDataSetChanged();//触发getView方法执行 这个时候getView实际上会拿到刚刚缓存好的图片
}
}
/**
* ImageLoadTask(内部类)继承AsyncTask相当于新开了一个线程
* 本地加载sd卡,ImageView模式
* */
class ImageLoadTaskImageView extends AsyncTask<Object, Void, Bitmap>{
String url;
@Override
protected Bitmap doInBackground(Object... params) {
url=(String) params[0];
Bitmap drawable=loadImageFromSd(url);
return drawable;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result==null) {
return;
}
addImage2Cache(url, result);//放入缓存
}
}
/**
* 连接网络下载图片方法
* */
public Bitmap loadImageFromInternet(String url) {
Bitmap bitmap = null;
HttpClient client = AndroidHttpClient.newInstance("Android");
HttpParams params = client.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3000);
HttpConnectionParams.setSocketBufferSize(params, 3000);
HttpResponse response = null;
InputStream inputStream = null;
HttpGet httpGet = null;
try {
httpGet = new HttpGet(url);
response = client.execute(httpGet);
int stateCode = response.getStatusLine().getStatusCode();
if (stateCode != HttpStatus.SC_OK) {
Log.d(TAG, "func [loadImage] stateCode=" + stateCode);
return bitmap;
}
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
inputStream = entity.getContent();
return bitmap = BitmapFactory.decodeStream(inputStream);
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (ClientProtocolException e) {
httpGet.abort();
e.printStackTrace();
} catch (IOException e) {
httpGet.abort();
e.printStackTrace();
} finally {
((AndroidHttpClient) client).close();
}
return bitmap;
}
/**
* 本地sd卡获取图片方法
* */
public Bitmap loadImageFromSd(String url) {
Bitmap bitmap=null;
bitmap=BitmapFactory.decodeFile(url);
while(bitmap==null){
bitmap=BitmapFactory.decodeFile(url);
}
return bitmap;
}
}
使用
Adapter中
if(!mBusy){
loader.loadImageListView(url, this, vh);
}else{
Bitmap bitmap = loader.getBitmapFromCache(url);
if(bitmap != null){
vh.menu_iv.setImageBitmap(bitmap);
}else{
vh.menu_iv.setImageResource(R.drawable.default_s);
}
}
附1:LruCache类讲解
LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除。
其中用到的数据对象是LinkedHashMap,所以不要把这个类想的多么深不可测,还是数据结构 + 算法。既然用到了这个map,自然就要有添加修改和删除操作了,用到了最近最少使用算法,自然就要用到优先级了。
作为缓存,肯定有一个缓存的大小,这个大小是可以设定的(自定义sizeOf())。当你访问了一个item(需要缓存的对象),这个item应该被加入到内存中,然后移动到一个队列的顶部,如此循环后这个队列的顶部应该是最近访问的item了,而队尾部就是很久没有访问的item,这样我们就应该对队尾部的item优先进行回收操作。
因为用到了HashMap,那么就有这个数据存储对象的特点(KEY-VALUE),放入这个map的item应该会被强引用,要回收这个对象的时候是让这个key为空,这样就让有向图找不到对应的value,最终被GC。
缓存的最大特点是不做重复的劳动,如果你之前已经缓存过这个item了,当你再次想要缓存这个item时,应该会先判断是否已经缓存好了,如果已经缓存,那么就不执行添加的操作。
我们应该能通过某个方法来清空缓存,这个缓存在app被退出后就自动清理,不会常驻内存。
sizeof()方法。这个方法默认返回的是你缓存的item数目,如果你想要自定义size的大小,直接重写这个方法,返回自定义的值即可。
附2:LinkedHashMap讲解
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。