一、前言:
之前写过一个简单的网络图片读取工具,读取网络图片,在Android应用中经常可见,在ListView、GridView这些控件中尤其可见。而为了提高用户体验,这些控件图片的读取,一般都是采用异步加载的方式,而使用缓存则是必不可少的环节。 现在网络上已经出现了很多功能丰富、使用简单的图片下载框架,例如universalimageloader,使用只需要初始化并且传入参数即可。这里我打算自己实现一个功能简单的图片下载工具类,采取了内存缓存+磁盘缓存+网络获取的三级缓存方式。
二、思路:
三级缓存,在网上查阅过很多资料,个人所知,有两种实现方式:
1、软引用+SD卡缓存+网络获取
2、Lrucache+DiskLruCache+网络获取
因为从 android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。所以本文的思路是:先从内存缓存中寻找需要的图片,如果内存中不存在该图片,再从磁盘缓存中查找,如果再查找为空,则开启网络获取。
三、相关技术:
这里主要使用第二种方式实现:
1、关于Lrucache:
Lrucache是Android自带的一个内存缓存类,专门用来做图片缓存处理的。它有一个特点,当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。
详细使用方法请自行百度。
2、关于DiskLruCache:
DiskLruCache是一个非Google官方编写,但获得官方认证的缓存类。它的作用是把图片(或者其他对象)保存到本地磁盘中,以便下次需要的时候,直接获取。
DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/
由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来,然后手动添加到项目当中。DiskLruCache的源码在Google Source上,地址:android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java。
详细使用方法自行百度。
四、ImageLoad实现类:
package com.zero.imageload;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Set;
/**
* @author Zero
* @version 1.0
* @date 2015/7/29
*/
public class ImageLoad {
private static ImageLoad mImageLoad = null;
private static String CacheName = "image"; //图片缓存文件名
/**
* 获取唯一实例
* @return
*/
public static ImageLoad getInstance(Context context) {
if (mImageLoad == null) {
mImageLoad = new ImageLoad(context.getApplicationContext());
}
return mImageLoad;
}
/**
* ImageLoad停止所有的任务
* 通常在退出活动时,用于取消下载任务
*/
public void ImageLoadCancel(){
cancelAllTasks();
}
/**
* ImageLoad输出到日志
* 通常到活动暂停时,用于刷新cache
*/
public void ImageLoadPause(){
fluchCache();
}
/**
* ImageLoad获取磁盘缓存的大小
*/
public long ImageLoadCacheSize(){
return mDiskLruCache.size();
}
/**
* ImageLoad清理内存缓存
*/
public void ImageLoadCacheClean(){
//清理内存缓存
clearCache();
}
/**
* ImageLoad磁盘缓存清理
*/
public void ImageLoadDiskCacheClean(){
try {
mDiskLruCache.delete();
} catch (IOException e) {
e.printStackTrace();
}
System.gc();
}
/**
* ImageLoad清理所有的数据
*/
public void ImageLoadCacheAllClean(){
//清理内存缓存
clearCache();
//清理磁盘缓存
try {
mDiskLruCache.delete();
} catch (IOException e) {
e.printStackTrace();
}
System.gc();
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
* @param imageUrl
* @param bitmapListener
*/
public void loadBitmap(String imageUrl,BitmapListener bitmapListener) {
//开启一个新的异步任务
BitmapWorkerTask task = new BitmapWorkerTask(bitmapListener);
//把任务加进任务集合
taskCollection.add(task);
//任务开始
task.execute(imageUrl);
}
/**
* 加载Bitmap对象
* @param imageUrl
* @param width
* @param height
* @param bitmapListener
*/
public void loadBitmap(String imageUrl,int width,int height,BitmapListener bitmapListener){
}
/**
* 加载Bitmap对象,由于图片占用内存过大,在这里直接跳过内存缓存,存到磁盘缓存
* @param imageUrl
* @param bitmapListener
*/
public void loadBigBitmap(String imageUrl,BitmapListener bitmapListener){
}
/**
* 记录所有正在下载或等待下载的任务。
*/
private Set taskCollection;
/**
* 为每个任务定义一个接口
*/
public interface BitmapListener{
public void onFailure();
public void onSuccess(Bitmap bitmap);
}
/**
* 异步下载图片的任务。
*
* @author zero
*/
class BitmapWorkerTask extends AsyncTask {
/**
* 图片的URL地址
*/
private String imageUrl;
/**
* 绑定监听器
*/
private BitmapListener mBitmapListener;
/**
* 构造方法
* @param bitmapListener
*/
public BitmapWorkerTask(BitmapListener bitmapListener){
this.mBitmapListener = bitmapListener;
}
/**
* 异步类执行
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(String... params) {
//获取URL
imageUrl = params[0];
Bitmap bitmap;
//先从缓存中获取
bitmap = mMemoryCache.get(imageUrl);
if(bitmap != null)
return bitmap;
//再从磁盘缓存中获取
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapShot = null;
try {
// 生成图片URL对应的key
final String key = hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
}
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if (bitmap != null) {
// 将Bitmap对象添加到内存缓存当中
addBitmapToMemoryCache(params[0], bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
return null;
}
/**
* 异步类执行返回
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if(bitmap == null)
mBitmapListener.onFailure();
else mBitmapListener.onSuccess(bitmap);
//移除任务
taskCollection.remove(this);
}
}
/**
* 取消所有正在下载或等待下载的任务。
*/
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
/**
* 构造方法,由于是单例,因此构造方法为私有
*/
private ImageLoad(Context context) {
// 初始化任务列表
taskCollection = new HashSet();
// 初始化内存缓存类
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
// 初始化磁盘缓存类
try {
// 获取图片缓存路径
File cacheDir = getDiskCacheDir(context, CacheName);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache mMemoryCache;
/**
* 将一张图片存储到LruCache中。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @param bitmap LruCache的键,这里传入从网络上下载的Bitmap对象。
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 删除bitmap
*
* @param key
*/
public void deleteBitmapFromMemoryCache(String key) {
mMemoryCache.remove(key);
}
/**
* 清空内存缓存
*
* @return void
*/
public void clearCache() {
if (mMemoryCache != null) {
if (mMemoryCache.size() > 0) {
Log.d("CacheUtils", "mMemoryCache.size() " + mMemoryCache.size());
mMemoryCache.evictAll();
Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
}
// 初始化内存缓存类
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
}
/**
* 图片硬盘缓存核心类, 用于缓存所有下载好的图片到本地
*/
private DiskLruCache mDiskLruCache;
/**
* 获取当前应用程序的版本号。
*/
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;
}
/**
* 根据传入的uniqueName获取硬盘缓存的路径地址。
*/
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* 使用MD5算法对传入的key进行加密并返回。
*/
public 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 String bytesToHexString(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();
}
/**
* 将缓存记录同步到journal文件中。
*/
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @param urlString 图片的URL地址
* @return 解析后的Bitmap对象
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
}
五、使用方法:ImageLoad.getInstance(context).loadBitmap(imageurl, new BitmapListener() {
@Override
public void onSuccess(Bitmap bitmap) {
// TODO 自动生成的方法存根
ImageView image = (ImageView) v.findViewById(R.id.image);
image.setImageBitmap(bitmap);
}
@Override
public void onFailure() {
// TODO 自动生成的方法存根
}
});
六、不足之处
这个工具类目前只提供了一个图片下载方法,而且尚未做一些防止OOM发生的操作,尚有很多提高的地方,希望大家给些意见,大家一起互相交流,请狠狠得拍我。 - - 哈哈。
转载自: http://blog.csdn.net/z82367825/article/details/46509235