非常轻量级的ImageLoader
@(Android)[工具类]
又要开始重复造轮子了,这篇主要是参照郭霖大神的 Android照片墙完整版,完美结合LruCache和DiskLruCache ,和很久以前再慕课网看的一个高效使用ListView教程,然后自己使用DiskLruCache和LruCache写了一个超级轻量级得ImageLoader,有多轻量级呢?以后加载图片只需要一句话:
imageloader.loadBitmap(imgUrl, imageview);
并且 ImageLoader只用来记载图片,并有自带缓存功能。既然说得这么屌,就上代码吧!
先看一下工程:
ImageLoader主要就是一个Java文件,但是由于用到了DiskLruCache,所以需要将其添加进来。引入 DiskLruCache 也相当简单,就是把 DiskLruCache.java 文件放在src的 libcore.io
包下就行了,上面工程截图可以看看。
方便大家下载 DiskLruCache,这里贴一下下载地址:
http://download.csdn.net/detail/sinyu890807/7709759
ImageLoader.java
public class Imageloader {
public static final int MAX_DISK_CACHE_SIZE = 10 * 1024 * 1024;
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private Set<BitmapWorkerTask> mTasks;
public Imageloader(Context context) {
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
File cacheDir = new File(getDiskCacheDir(context, "img_thumb"));
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
try {
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, MAX_DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
mTasks = new HashSet<>();
}
public String getDiskCacheDir(Context context, String uniqueName) {
String cacheDir;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
cacheDir = context.getExternalCacheDir().getPath();
} else {
cacheDir = context.getCacheDir().getPath();
}
return cacheDir;
}
public 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 void loadBitmap(String imgUrl, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemoryCache(imgUrl);
if (null != bitmap && null != imageView) {
imageView.setImageBitmap(bitmap);
return;
}
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
mTasks.add(task);
task.execute(imgUrl);
}
public void cancelAllTasks() {
for (AsyncTask task : mTasks) {
task.cancel(false);
}
}
public void flushCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void addBitmapToMemoryCache(String imgUrl, Bitmap bitmap) {
if (null == mMemoryCache.get(imgUrl)) {
mMemoryCache.put(imgUrl, bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String imgUrl) {
return mMemoryCache.get(imgUrl);
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private String imgUrl;
private ImageView imageView;
public BitmapWorkerTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... params) {
imgUrl = params[0];
FileDescriptor fileDescriptor = null;
FileInputStream fis = null;
DiskLruCache.Snapshot snapshot = null;
try {
String key = hashKeyForDisk(imgUrl);
snapshot = mDiskLruCache.get(key);
if (null == snapshot) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream os = editor.newOutputStream(0);
if (downloadUrlStream(imgUrl, os)) {
editor.commit();
} else {
editor.abort();
}
}
}
snapshot = mDiskLruCache.get(key);
if (null != snapshot) {
fis = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fis.getFD();
}
Bitmap bitmap = null;
if (null != fileDescriptor) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if (bitmap != null) {
addBitmapToMemoryCache(imgUrl, bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (null != imageView && null != bitmap) {
imageView.setImageBitmap(bitmap);
}
mTasks.remove(this);
}
}
public String hashKeyForDisk(String key) {
String cacheKey;
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes());
cacheKey = byteToHexString(digest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String byteToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
private boolean downloadUrlStream(String urlStr, OutputStream outputStream) {
HttpURLConnection connection = null;
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
InputStream is = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
bis = new BufferedInputStream(connection.getInputStream());
bos = new BufferedOutputStream(outputStream);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = bis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
代码挺长的,主要看几个东西:
1. loadBitmap(String imgUrl, ImageView imageView)
public void loadBitmap(String imgUrl, ImageView imageView) {
//从缓存中取bitmap
Bitmap bitmap = getBitmapFromMemoryCache(imgUrl);
//如果取到,直接设置给imageview
if (null != bitmap && null != imageView) {
imageView.setImageBitmap(bitmap);
return;
}
//未取到,则启动task取下载
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
mTasks.add(task);
task.execute(imgUrl);
}
BitmapWorkerTask
主要完成工作就是取下载图片,并且将其缓存到DiskLruCache中。使用的key是 imgUrl 的 MD5 编码。
最主要的就是上面两个内容,其中还设计了 LruCache 和 DiskLruCache的使用,大家自己找文章看看呗
使用方法
这里我们用ListView为例子,假设现在我们有一个这样一个实体类:
public class News {
private String name;
private String picSmall;
private String picBig;
private String description;
private int learnerCount;
public News(String name, String picSmall, String picBig, String description, int learnerCount) {
this.name = name;
this.picSmall = picSmall;
this.picBig = picBig;
this.description = description;
this.learnerCount = learnerCount;
}
//....
}
我们已经从网络上获取到了一个List<News> data
的数据,现在我们要将数据用 ListView 来显示:
mListView.setAdapter(new NewsAdapter(MainActivity.this, data, mListView));
我们自定义的Adapter的 getView()
方法应该是这样, 加载图片只用了一行代码:
imageloader.loadBitmap(data.get(position).getPicSmall(), holder.smallpic);
@Override
public View getView(int position, View convertView, ViewGroup father) {
ViewHolder holder = null;
if (convertView != null) {
holder = (ViewHolder) convertView.getTag();
} else {
convertView = this.inflater.inflate(R.layout.news_layout, null, false);
holder = new ViewHolder();
holder.smallpic = (ImageView) convertView.findViewById(R.id.iv_smallpic);
holder.desc = (TextView) convertView.findViewById(R.id.tv_desc);
holder.name = (TextView) convertView.findViewById(R.id.tv_name);
holder.learner = (TextView) convertView.findViewById(R.id.tv_leaner);
convertView.setTag(holder);
}
holder.smallpic.setImageResource(R.mipmap.ic_launcher);
holder.name.setText(this.data.get(position).getName());
holder.desc.setText(this.data.get(position).getDescription());
holder.learner.setText("" + this.data.get(position).getLearnerCount());
//使用Imageloader加载图片
imageloader.loadBitmap(data.get(position).getPicSmall(), holder.smallpic);
return convertView;
}
轻松搞定,并且ImageLoader已经做了缓存,下次打开应用,没有网络的情况下同样可以访问图片。
当然,这样加载是一次加载 ListView的 所有 item,肯定是不对的。正确的是 ListView 滑动到哪里,就显示哪里。郭霖大神博客中有提到,大家可以自己看看。
整个工程
为了让大家看明白,这里附上整个工程代码,可以细细品味一下: