一、LruCache
LruCache是一套内存缓存的解决方案,算法基于LRU。
LRU:Least Recently Used(近期最少使用)。LruCache基于LRU算法的缓存策略。
LruCache是一个泛型类,其以强引用的方式存储外界的缓存对象。当内存缓存达到设定的最大值时,则将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。
一、LruCache的基本使用
LruCache一般使用来缓存图片,下面以缓存Bitmap为例
建立一个key为String类型,value为Bitmap类型的LruCache
private LruCache<String, Bitmap> mCache;
初始化LruCache,并指定LruCache的缓存大小
//获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); //指定缓存大小 int cacheSize = maxMemory / 8; mCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { //返回Bitmap的实际大小 (单位应与maxMemory一致) return value.getByteCount(); } };
获取缓存
mCache.get(key);
添加缓存
mCache.put(key,value);
移除某个键的缓存
mCache.remove(key);
清空缓存
mCache.evictAll()
二、使用LruCache封装图片加载缓存器
根据LruCache,我们可以将它封装在LruCacheImageLoader来对图片进行缓存。设计模式为使用单例模式来设计:
在Logcat中打印异步任务个数:
package com.cxmscb.cxm.cacheproject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by cxm on 2016/8/25.
*/
public class LruCacheImageLoader {
private LruCache<String,Bitmap> mCache;
// 存储异步任务的集合
private Set<LruCacheAsyncTask> mTaskSet;
/*ImageLoader的单例*/
private static LruCacheImageLoader mImageLoader;
private Context mContext;
private LruCacheImageLoader(Context context){
this.mContext = context;
mTaskSet = new HashSet<>();
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 初始化LruCache
mCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}
// 获取LruCacheImageLoader的实例(带同步锁)
public static LruCacheImageLoader getInstance(Context context){
if(mImageLoader==null){
synchronized (LruCacheImageLoader.class){
if(mImageLoader==null)
mImageLoader = new LruCacheImageLoader(context);
}
}
return mImageLoader;
}
// 根据key值获取缓存中的图片
private Bitmap getBitmapFromMemory(String url){
return mCache.get(url);
}
//将一张图片存储到LruCache中。
private void putBitmapToMemory(String url, Bitmap bitmap) {
if (getBitmapFromMemory(url) == null) {
mCache.put(url, bitmap);
}
}
/*------------以上的LruCache的使用-------------*/
/*
* 对外方法:普通地加载图片到imageView中
*/
public void displayImage(ImageView iv, final String url) {
//从缓存中取出图片
Bitmap bitmap = getBitmapFromMemory(url);
//如果缓存中没有,先设为默认图片
if (bitmap == null) {
LruCacheAsyncTask task = new LruCacheAsyncTask(iv);
task.execute(url);
mTaskSet.add(task);
} else {
//如果缓存中有 直接设置
iv.setImageBitmap(bitmap);
}
}
/**
* 对外方法:为listview加载从start到end的所有的Image
*
*/
public void loadTagImageViewInListView(int start, int end, String[] tagUrls, ListView mListView) {
Drawable.ConstantState aConstantState = mContext.getResources().getDrawable(R.drawable.loading).getConstantState();
for (int i = start; i < end; i++) {
String url = tagUrls[i];
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
// 判断图片是否加载过,以免新建多个asynctask
if (imageView.getDrawable().getConstantState().equals(aConstantState)) {
displayImage(imageView, url);
}
}
Log.i("num of asynctask"," "+mTaskSet.size());
}
private class LruCacheAsyncTask extends AsyncTask<String,Void,Bitmap>{
private ImageView imageView;
public LruCacheAsyncTask(ImageView imageView){
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... strings) {
Bitmap bitmap = getBitmapFromUrl(strings[0]);
// 将bitmap缓存到LruCache中
if(bitmap!=null){
putBitmapToMemory(strings[0],bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
mTaskSet.remove(this);
Log.i("num of asynctask"," "+mTaskSet.size());
}
private Bitmap getBitmapFromUrl(String urlPath) {
Bitmap bitmap = null;
try {
URL url = new URL(urlPath);
URLConnection conn = url.openConnection();
conn.connect();
InputStream in;
in = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(in);
// TODO Auto-generated catch block
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
/**
* 停止所有当前正在运行的任务
*/
public void cancelAllTask() {
if (mTaskSet != null) {
for (LruCacheAsyncTask task : mTaskSet) {
task.cancel(false);
}
mTaskSet.clear();
Log.i("num of asynctask"," "+mTaskSet.size());
}
}
}
三、 使用LruCacheImageLoader来加载网络图片:
一、项目效果图:
首先是ListView上下滑动时不加载图片,停止滑动才开始加载图片
在Logcat中查看加载图片的异步任务个数:
滑动时异步任务个数为0,滑动停止时任务数为ListView中的item可见数,然后递减,最后为0,再次滑动/停止时一直为0。主要是利用了下面的优化。
二、对ListView加载图片的优化:
监听ListView滚动状态,只有当其静止的时候才异步加载网络图片数据。(第一次进入也加载图片)
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if(scrollState==SCROLL_STATE_IDLE){
lruCacheImageLoader.loadTagImageViewInListView(mStart,mEnd,urls,listView);
}else {
// 以免产生太多无用的asynctask
lruCacheImageLoader.cancelAllTask();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisbleItem + visibleItemCount;
if(mFirstIn && visibleItemCount > 0){
lruCacheImageLoader.loadTagImageViewInListView(mStart,mEnd,urls,listView);
mFirstIn = false;
}
}
});