众所周知,在listview中加载多图时容易造成OOM,不过解决的办法由很多种,郭神的博客中也有很多类似的博客,我参照他的博客和群里的小伙伴七七(他提供的demo),写了这篇图片二级缓存的博客,其实图片的二级缓存在很多的框架中早已帮你封装好了,但是知其然,也要知其所以然。
主activity我就不写了下面我会提供demo,主要就是两图片的网址传给adapter中,下面着重就是adapter中的代码
public class MyAdapter extends BaseAdapter implements OnScrollListener {
private List<Bean> mList;
private LayoutInflater mInflater;
// 一级缓存
private LruCache<String, Bitmap> mCache;
// 二级缓存
private DiskLruCache mDiskLruCache;
// 当前显示在界面上的元素序号
private int mStart;
private int mEnd;
// 配合元素序号的URL图片链接
private String[] mUrls;
private ListView mListView;
// 异步任务栈
private Set<MyAsyncTask> mSet;
// 首次预加载标记
private boolean mFirst;
public MyAdapter(Context context, List<Bean> list, ListView listView) {
this.mList = list;
this.mInflater = LayoutInflater.from(context);
this.mListView = listView;
mListView.setOnScrollListener(this);
mSet = new HashSet<MyAsyncTask>();
mFirst = true;
mUrls = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
mUrls[i] = list.get(i).getImageUrl();
}
// 一级缓存初始化
long memory = Runtime.getRuntime().maxMemory();
int maxSize = (int) (memory / 8);
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
// 二级缓存初始化
try {
// 获取图片缓存路径
File disCacheDir = DiskLruCache.getDiskCacheDir(context, "bitmap");
if (!disCacheDir.exists()) {
disCacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache.open(disCacheDir,
DiskLruCache.getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* baseAdapter区域
*/
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item, null);
}
Bean bean = mList.get(position);
// 绑定viewholder
TextView title = ViewHolder.get(convertView, R.id.title);
TextView content = ViewHolder.get(convertView, R.id.content);
ImageView image = ViewHolder.get(convertView, R.id.imageView);
// 设置标记
image.setTag(bean.getImageUrl());
// 只从一级缓存里面取
Bitmap bitmap = getBitmapToCache(bean.getImageUrl());
if (bitmap == null) {
image.setImageResource(R.drawable.ic_launcher);
} else {
image.setImageBitmap(bitmap);
}
title.setText(bean.getTitle());
content.setText(bean.getContent());
return convertView;
}
/**
* 一级缓存
*
* @param key
* @param value
*/
// 添加到缓存
public void putBitmapToCache(String key, Bitmap value) {
mCache.put(key, value);
}
// 从缓存中取
public Bitmap getBitmapToCache(String key) {
return mCache.get(key);
}
// 将缓存记录同步到journal文件中
//方便每次判断硬盘中数据是否存在
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 二级缓存
*
* 从二级缓存中获取图片, 如果二级缓存中都没有,
* 那就去网络上拉去图片并存入本地,
* 然后解析成bitmap存入一级缓存 然后显示出来
*
* @param url
* @return
*/
public Bitmap getBitmapToDisCache(String url) {
Bitmap bitmap = null;
OutputStream os = null;
Snapshot snapshot = null;
FileInputStream fis = null;
FileDescriptor fileDescriptor = null;
// MD5加密
String key = HashKeyForMD5.hashKeyForDisk(url);
try {
// 从硬盘中检查journal中有没有key记录
snapshot = mDiskLruCache.get(key);
// 如果没有,则准备从网络中获取
// 首先判断网络连接是否正常,打开情况下才网络获取,否则显示默认图片
if (snapshot == null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
os = editor.newOutputStream(0);
// 下载到本地
if (loadImage(url, os)) {
editor.commit();
} else {
editor.abort();
}
}
}
// 从网络上下载好图片到硬盘后,再次从硬盘中获取
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
fis = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fis.getFD();
}
// 然后解析读取的数据成bitmap
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
// 如果解析成功
if (bitmap != null) {
// 添加到一级缓存
putBitmapToCache(url, bitmap);
}
return bitmap;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (snapshot != null) {
snapshot.close();
}
if (fis != null) {
fis.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return bitmap;
}
/**
* 从网络中拉去图片并存入本地
*
* @param url
* @param os
*/
private boolean loadImage(String url, OutputStream os) {
InputStream is = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
is = new URL(url).openStream();
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
int len = 0;
byte[] buf = new byte[1024*8];
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (bos!=null) {
bos.close();
}
if (bis!=null) {
bis.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return false;
}
/**
* 异步加载线程
*
* @author asus
*
*/
class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
private ImageView image;
private String url;
public MyAsyncTask(ImageView image, String url) {
this.image = image;
this.url = url;
}
@Override
protected Bitmap doInBackground(String... params) {
// 从一级缓存中取
Bitmap bitmap = getBitmapToCache(url);
if (bitmap == null) {
// 从二级缓存中取
bitmap = getBitmapToDisCache(url);
}
return bitmap;
}
// UI线程
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (image.getTag().equals(url)) {
image.setImageBitmap(result);
}
}
}
/**
*
* 滑动监听
*/
// 加载可见项的图片
public void firstToEnd(int first, int end) {
for (int i = first; i < end; i++) {
String url = mUrls[i];
// 从ListView中根据tag获取到imageView
ImageView image = (ImageView) mListView.findViewWithTag(url);
// 下载
MyAsyncTask myTask = new MyAsyncTask(image, url);
// 添加到线程管理器
mSet.add(myTask);
myTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
}
// 取消所以加载
public void cancelAllTask() {
if (mSet != null) {
for (MyAsyncTask task : mSet) {
task.cancel(false);
}
}
}
// 滑动状态改变
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
// 加载可见项
firstToEnd(mStart, mEnd);
} else {
// 停止所以的下载任务
cancelAllTask();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = visibleItemCount + firstVisibleItem;
// 首次启动预加载
if (mFirst && visibleItemCount > 0) {
firstToEnd(mStart, mEnd);
mFirst = false;
}
}
}
上面就是adapter的详细代码了,接下来分析一下
首先这个adapter接口了OnScrollListener,当点击程序进去时,首先会执行onSrcoll中的方法
if (mFirst && visibleItemCount > 0) {
firstToEnd(mStart, mEnd);
mFirst = false;
}
判断当前程序是不是第一次开始,并且item数不为0,然后
// 加载可见项的图片
public void firstToEnd(int first, int end) {
for (int i = first; i < end; i++) {
String url = mUrls[i];
// 从ListView中根据tag获取到imageView
ImageView image = (ImageView) mListView.findViewWithTag(url);
// 下载
MyAsyncTask myTask = new MyAsyncTask(image, url);
// 添加到线程管理器
mSet.add(myTask);
myTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
}
然后开启线程,将当先item所对应的url图片添加到线程管理器中开始下载
接下来在getView中
// 设置标记
image.setTag(bean.getImageUrl());
// 只从一级缓存里面取
Bitmap bitmap = getBitmapToCache(bean.getImageUrl());
if (bitmap == null) {
image.setImageResource(R.drawable.ic_launcher);
} else {
image.setImageBitmap(bitmap);
}
// 从缓存中取
public Bitmap getBitmapToCache(String key) {
return mCache.get(key);
}
先给图片用容器设置一个标志,这是防止图片错乱,这里的话可以去参照郭神的防止图片错乱那篇博客,里面很详细的介绍了各种方法。
放置图片时,我们先从内存中寻找有没有这张图片的缓存,有的话,填进去,没有的话,从SD卡的缓存中寻找,这就是二级缓存。
下面我们分析是如何把下载图片并且存进内存和sd卡中的
// 从一级缓存中取
Bitmap bitmap = getBitmapToCache(url);
if (bitmap == null) {
// 从二级缓存中取
bitmap = getBitmapToDisCache(url);
}
在线程中,我们先根据传进来的url判断当前的一级缓存(内存)和二级缓存(SD卡)中有没有当前url对应的图片,如果有,就返回,没有的话,下载后返回。
String key = HashKeyForMD5.hashKeyForDisk(url);
try {
// 从硬盘中检查journal中有没有key记录
snapshot = mDiskLruCache.get(key);
// 如果没有,则准备从网络中获取
// 首先判断网络连接是否正常,打开情况下才网络获取,否则显示默认图片
if (snapshot == null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
os = editor.newOutputStream(0);
// 下载到本地
if (loadImage(url, os)) {
editor.commit();
} else {
editor.abort();
}
}
}
// 从网络上下载好图片到硬盘后,再次从硬盘中获取
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
fis = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fis.getFD();
}
// 然后解析读取的数据成bitmap
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
// 如果解析成功
if (bitmap != null) {
// 添加到一级缓存
putBitmapToCache(url, bitmap);
}
return bitmap;
上面的过程就是将图片下载下来,然后存进SD卡和内存,然后返回给UI去处理,这里的SD卡缓存是开源框架DiskLruCache,原谅我还暂时理解不了其中的内容,不过这个你可以去hongyang的博客中,他里面有一篇关于这个的详细讲解,虽然我没看的太懂。
以上就是总的流程了,首先预加载首页图片,加载图片时会先从一级二级缓存中找,如果没有,就下载图片并将之存入缓存中,这个在上面的loadImage方法中,我就不贴了。最后为了防止快速滑动中图片加载的各种问题,这里设置了一个滑动时停止加载图片,停止滑动时加载当前页面的图片,有效的防止了图片的OOM和错乱问题。
// 滑动状态改变
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
// 加载可见项
firstToEnd(mStart, mEnd);
} else {
// 停止所以的下载任务
cancelAllTask();
}
}
如上,滑动时停止线程,停止时加载图片。
下面上一张效果图
demo下载地址:http://download.csdn.net/download/verzqli/9330971