很多android小白在第一次独立写项目的时候,一定会遇到一个头疼的问题,那就是图片。关于大部分的问题,在我的上一篇博客你真的会用ListView吗?中都讲到了,但是再往深处想,我们采用的LruCache缓存技术,是基于运行中的内存,于是在关掉程序后再次打开,图片还是得继续下载。那我们就将缓存的数据放在手机存储中啊,是的,DiskLruCache完美解决了这个问题,并且每一个app都有一个默认的缓存目录,在app卸载的时候回一并删除,这样就不用担心数据残留的问题了。
基于上一篇博客,我们要做的是完善ImageAdapter的代码:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import libcore.io.DiskLruCache;
public class ImageAdapter extends ArrayAdapter<String> implements AbsListView.OnScrollListener {
// 记录异步加载任务的集合
List<BitmapTask> taskList = new ArrayList<>();
// item布局id
private int resource;
// 绑定的控件ListView
private ListView mListView;
// 内存缓存,以键值的方式存储
private LruCache<String, Bitmap> mMemoryCache;
// 硬盘缓存,不能实例化
private DiskLruCache mDiskLruCache;
// 第一个可见项index
private int mFirstVisibleItem;
// 屏幕中可见项的数目count
private int mVisibleItemCount;
// 避免第一次进入不加载图片,设置bool参数
private boolean isFirstEnter = true;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
try {
// 获取图片缓存路径
File cacheDir = SystemUtil.getDiskCacheDir(context, "thumb");
if (!cacheDir.exists() && cacheDir.mkdirs()) {
Log.d("cjt-pc", "创建文件夹成功,路劲是:" + cacheDir.getAbsolutePath());
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache
.open(cacheDir, SystemUtil.getAppVersion(context), 1, 10 * 1024 * 1024);
Log.d("cjt-pc", "硬盘缓存大小为:" + mDiskLruCache.size());
} catch (IOException e) {
e.printStackTrace();
}
}
// 添加bitmap到lruCache缓存中
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 从lruCache缓存中获取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// 给imageView设置一个唯一标签Tag,避免图片显示混乱
viewHolder.imageView.setTag(url);
// 初始化imageView,防止在异步加载时出现上一张图片
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmap(url, viewHolder.imageView);
return convertView;
}
// 通过lruCache缓存技术,快速重新加载和处理图片
public void loadBitmap(String imageKey, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// 仅当ListView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
if (i == SCROLL_STATE_IDLE) {
loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
} else {
cancelAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
// 因此在这里为首次进入程序开启下载任务。
if (isFirstEnter && visibleItemCount > 0) {
loadBitmaps(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
*
* @param firstVisibleItem 第一个可见的ImageView的下标
* @param visibleItemCount 屏幕中总共可见的元素数
*/
private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
try {
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String imageUrl = Images.imageUrls[i];
Bitmap bitmap = getBitmapFromMemCache(imageUrl);
if (bitmap == null) {
BitmapTask task = new BitmapTask();
taskList.add(task);
task.execute(imageUrl);
} else {
ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将缓存记录同步到journal文件中。
*/
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void cancelAllTasks() {
if (!taskList.isEmpty()) {
// 遍历任务集合,取消正在进行的异步任务,然后清空任务列表
for (BitmapTask task : taskList) {
task.cancel(true);
}
taskList.clear();
}
}
class ViewHolder {
ImageView imageView;
}
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
DiskLruCache.Snapshot snapShot;
try {
// 生成图片URL对应的key
String key = SystemUtil.hashKeyForDisk(url);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
is = new URL(url).openStream();
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
// 将InputStream转换成OutputStream,然后提交到缓存中
OutputStream os = editor.newOutputStream(0);
int length;
byte[] buffer = new byte[1024];
while ((length = is.read(buffer)) != -1) {
os.write(buffer, 0, length);
}
editor.commit();
// 注意这里is在read(buffer)之后会发生改变,所以要重新获取一次
is = new URL(url).openStream();
} else {
is = snapShot.getInputStream(0);
}
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null) {
addBitmapToMemoryCache(url, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// 要记得在异步加载过程完成后将本次任务移出
taskList.remove(this);
}
}
}
这里面采用了一个工具类,SystemUtil.class:
package com.cjt_pc.listviewtest;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Created by cjt-pc on 2015/10/14.
* Email:879309896@qq.com
*/
public class SystemUtil {
// 得到当前版本号
public static int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
// 通过唯一的名字在指定的缓存目录下创建临时文件
public static File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && context.getExternalCacheDir() != null) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
// 传入指定的key,返回类型为MD5的String
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
这样,我们就不用担心在程序退出后下载完成的图片都被释放掉了,用户体验摆在首位。
源码下载:http://download.csdn.net/detail/qq_15002323/9180993
参考资料:
Android DiskLruCache完全解析,硬盘缓存的最佳方案
Android照片墙完整版,完美结合LruCache和DiskLruCache