三级缓存指内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。
关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。
按模块分:控制图片加载逻辑模块、图片下载引擎模块、文件缓存模块、内存缓存模块。
此模块主要封装了图片加载的逻辑,是一个异步任务。具体代码如下:
public class CacheImageAsyncTask extends AsyncTask<String, Integer, Bitmap> {
private ImageView imageView;
private ImageFileCache fileCache;
private ImageMemoryCache memoryCache;
private String imgType;
private String url;
private int toW = 0;
private int toH = 0;
public CacheImageAsyncTask(ImageView imageView) {
this.imageView = imageView;
fileCache = MyApplication.getFileCache();
memoryCache = MyApplication.getMemoryCache();
}
public CacheImageAsyncTask(ImageView imageView,int toW,int toH) {
this.imageView = imageView;
fileCache = MyApplication.getFileCache();
memoryCache = MyApplication.getMemoryCache();
this.toH = toH;
this.toW = toW;
}
/** * 加载图片给特定的imageview * * @param imageView */
public CacheImageAsyncTask(ImageView imageView, String imgType) {
this.imageView = imageView;
fileCache = MyApplication.getFileCache();
memoryCache = MyApplication.getMemoryCache();
this.imgType = imgType;
}
public Bitmap getBitmap(String url,int toW,int toH) {
// 从内存缓存中获取图片
Bitmap result = memoryCache.getBitmapFromCache(url);
if (result == null) {
// 文件缓存中获取
result = fileCache.getImage(url);
if (result == null) {
// 从网络获取
result = ImageGetFromHttp.downloadBitmap(url,toW,toH);
if (result != null) {
fileCache.saveBitmap(result, url);
memoryCache.addBitmapToCache(url, result);
}
} else {
// 添加到内存缓存
memoryCache.addBitmapToCache(url, result);
}
}
return result;
}
public Bitmap getBitmap(String url) {
return getBitmap(url,0,0);
}
protected Bitmap doInBackground(String... params) {
url = params[0];
if(toH!=0 && toW!=0){
return getBitmap(url,toW,toH);
}else{
return getBitmap(url);
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
if (imageView != null) {
if (imgType != null && imgType.equals("avatar")) {
bitmap = ImageUtil.getRoundBitmap(bitmap);
}
Object tag = imageView.getTag();
if (tag != null && tag instanceof String) {
String s = (String) tag;
if (s.equals(url)) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
内存缓存模块封装了lrucache。具体代码如下:
public class ImageMemoryCache {
/** * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。 */
private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量
private static LruCache<String, Bitmap> mLruCache; //硬引用缓存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; //软引用缓存
public ImageMemoryCache(Context context) {
int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 4; //硬引用缓存容量,为系统可用内存的1/4
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
return value.getRowBytes() * value.getHeight();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
// 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
};
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_SIZE) {
return true;
}
return false;
}
};
}
/** * 从缓存中获取图片 */
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//先从硬引用缓存中获取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
//如果硬引用缓存中找不到,到软引用缓存中找
synchronized (mSoftCache) {
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
//将图片移回硬缓存
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
mSoftCache.remove(url);
}
}
}
return null;
}
/** * 添加图片到缓存 */
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}
public class ImageFileCache {
private static final String CACHDIR = "wantToGoImgCache";
private static final String WHOLESALE_CONV = ".cach";
private static final int MB = 1024*1024;
private static final int CACHE_SIZE = 10;
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache() {
//清理文件缓存
removeCache(getDirectory());
}
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
File file = new File(getDirectory(),MD5Util.md5(url));
if (file.exists()) {
//Log.d("ImageFileCache","绝对路径:"+file.getAbsolutePath());
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath());
if (bmp == null) {
file.delete();
} else {
updateFileTime(file.getAbsolutePath());
return bmp;
}
}else{
//Log.d("ImageFileCache","文件不存在");
}
return null;
}
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足
return;
}
//进行映射
String filename = MD5Util.md5(url);
//获取缓存目录
File dirFile = getDirectory();
//缓存目录不存在,建立缓存目录
if (!dirFile.exists())
dirFile.mkdirs();
//新建文件对象
File file = new File(dirFile,filename);
try {
//在缓存文件下创建文件
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/** * 计算存储目录下的文件大小, * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 * 那么删除40%最近没有被使用的文件 */
private boolean removeCache(File dirFile) {
File[] files = dirFile.listFiles();
if (files == null) {
return true;
}
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return false;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 * files.length) + 1);
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
/** 修改文件的最后修改时间 **/
public void updateFileTime(String path) {
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
/** 获得缓存目录 **/
private File getDirectory() {
File dir = new File(getSDPathFile(),CACHDIR);
return dir;
}
/** 取SD卡路径 **/
private File getSDPathFile() {
File storageDirectory = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED); //判断sd卡是否存在
if (sdCardExist) {
storageDirectory = Environment.getExternalStorageDirectory(); //获取根目录
}
return storageDirectory;
}
/** * 根据文件的最后修改时间进行排序 */
private class FileLastModifSort implements Comparator<File> {
public int compare(File arg0, File arg1) {
if (arg0.lastModified() > arg1.lastModified()) {
return 1;
} else if (arg0.lastModified() == arg1.lastModified()) {
return 0;
} else {
return -1;
}
}
}
}
一般会在下载时对图片做一个二次采样的操作,防止oom
public class ImageGetFromHttp {
private static final String LOG_TAG = "ImageGetFromHttp";
public static Bitmap downloadBitmap(String url,int toW,int toH) {
Bitmap ret = null;
try {
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
conn.setRequestProperty("connection", "keep-alive");
conn.connect();
//获取输入流
InputStream inputStream = conn.getInputStream();
//读取图片数据到字节数组,便于后续的文件缓存操作,我们缓存的是二进制文件,而不是图片文件
byte[] data = StreamUtil.readStream(inputStream);
//对图片进行二次采样
//进行图片的解码
//1.进行图片的二次采样, 第一次获取图片尺寸,第二次缩放加载图片
// 指定图片解码的时候,采用的参数
BitmapFactory.Options opts = new BitmapFactory.Options();
// 仅获取图片的宽高,图像的像素信息全都不加载
// 不会占用太多内存
opts.inJustDecodeBounds = true;
// 注意,使用Options来设置解码的方式
Bitmap bitmap = BitmapFactory.decodeByteArray(
data,
0,
data.length,
opts
);
int picW = opts.outWidth;
int picH = opts.outHeight;
//Log.d("testCaiYangbefore", picW + " " + picH);
ret = ImagSampleUtil.getAfterBitmap(data, opts, toW, toH);
//Log.d("testCaiYangafter", ret.getWidth() + " " + ret.getHeight());
inputStream.close();
//断开网络连接
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ret;
}
}
ima = (ImageView) findViewById(R.id.imageView1);
new CacheImageAsyncTask(ima,this).execute("http://zhibo.tianyuan161.com/uploads/start_logo/2014/0429/d33063dfed90e18a8135156f97f02177.png");
代码初稿来自csdn,做了部分优化。
https://github.com/zhujainxipan/FYForAndroidTest