Android 三级缓存Cache(内存,文件,网络)图片缓存设计
1.简介
大家都知道,在我们Android 开发的过程中,对于图片的处理,是非常重要的,而对于我们如果每次都重网络去拉去图片,那样会造成,现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如淘宝,qq的照片墙)中比较多。
现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。
2.图片缓存的原理
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。
关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。
3.实例源码
(1)内存缓存
- package com.zengtao.tools;
- import java.lang.ref.SoftReference;
- import java.util.LinkedHashMap;
- import android.annotation.SuppressLint;
- import android.app.ActivityManager;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.util.LruCache;
- /**
- * 内存缓存:两层缓存
- *
- * @author zengtao 2015年4月27日 上午10:39:23
- */
- public class MemoryCache {
- private final static int SOFT_CACHE_SIZE = 15; // 软引用缓存容量
- private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存
- private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存
- @SuppressLint("NewApi")
- public MemoryCache(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();
- }
- 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 = 1L;
- @Override
- protected boolean removeEldestEntry(
- java.util.Map.Entry<String, SoftReference<Bitmap>> eldest) {
- if (size() > SOFT_CACHE_SIZE) {
- return true;
- }
- return false;
- }
- };
- }
- /**
- * 存储图片到缓存
- *
- * @param url
- * :key
- * @param bitmap
- * : 图片
- */
- @SuppressLint("NewApi")
- public void saveBitmap(String url, Bitmap bitmap) {
- if (bitmap != null) {
- synchronized (bitmap) {
- mLruCache.put(url, bitmap);
- }
- }
- }
- /**
- * 获取缓存图片
- *
- * @param url
- * :url
- * @return
- */
- @SuppressLint("NewApi")
- public Bitmap getBitmap(String url) {
- Bitmap bitmap = null;
- // 从硬引用找
- synchronized (mLruCache) {
- // 从硬引用中获取
- bitmap = mLruCache.get(url);
- if (bitmap != null) {
- // 如果找到了,将元素移动到linkendHashMap的最前面,从而保证lrd算法中的是最后删除
- mLruCache.remove(url);
- mLruCache.put(url, bitmap);
- return bitmap;
- }
- }
- // 硬引用没找到,从软引用找
- synchronized (mSoftCache) {
- SoftReference<Bitmap> softReference = mSoftCache.get(url);
- if (softReference != null) {
- bitmap = softReference.get();
- // 如果找到了,重新添加到硬缓存中
- mLruCache.put(url, bitmap);
- mSoftCache.remove(url);
- return bitmap;
- } else {
- mSoftCache.remove(url);
- }
- }
- return null;
- }
- public void clearCache() {
- mSoftCache.clear();
- }
- }
(2)文件缓存
- package com.zengtao.tools;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.OutputStream;
- import java.util.Arrays;
- import java.util.Comparator;
- import android.annotation.SuppressLint;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Environment;
- import android.os.StatFs;
- /**
- * 文件缓存
- *
- * @author zengtao 2015年4月27日 上午11:49:52
- */
- public class FileCache {
- private final static String IMAGECACHE = "ImageCache";
- private final static String lASTPATHNAME = ".cache"; // 文件名
- private final static int MB = 1024 * 1024;
- private final static int CACHESIZE = 10;
- private final static int SDCARD_FREE_SPANCE_CACHE = 10;
- public FileCache() {
- removeCache(getDirectory());
- }
- /**
- * 将图片存入缓存
- *
- * @param url
- * : 地址
- * @param bitmap
- * : 图片
- */
- public void saveBitmap(String url, Bitmap bitmap) {
- if (bitmap == null) {
- return;
- }
- if (SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
- return; // 空间不足
- }
- String fileName = convertUrlToFileName(url);
- String dirPath = getDirectory();
- File dirFile = new File(dirPath);
- if (dirFile.exists()) {
- dirFile.mkdirs();
- }
- File file = new File(dirPath + "/" + fileName);
- try {
- file.createNewFile();
- OutputStream outputStream = new FileOutputStream(file);
- bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
- outputStream.flush();
- outputStream.close();
- } catch (Exception e) {
- System.out.println("文件未找到或者io异常");
- }
- }
- /**
- * 获取文件缓存图片
- *
- * @param url
- * : 地址
- * @return : bitmap
- */
- public Bitmap getBitmap(final String url) {
- Bitmap bitmap = null;
- final String path = getDirectory() + convertUrlToFileName(url);
- File file = new File(path);
- if (file.exists()) {
- bitmap = BitmapFactory.decodeFile(path);
- if (bitmap == null) {
- file.delete();
- } else {
- updateFileTime(path);
- }
- }
- return bitmap;
- }
- /**
- * 获取sdCard路径
- *
- * @return :路径地址
- */
- private String getSDCardPath() {
- String path = "";
- File file = null;
- boolean isSDCardExist = Environment.getExternalStorageState()
- .toString().equals(android.os.Environment.MEDIA_MOUNTED); // 判断是否有sdCard
- if (isSDCardExist) {
- file = Environment.getExternalStorageDirectory();
- }
- if (file != null) {
- path = file.toString();
- }
- return path;
- }
- /**
- * 计算存储目录下的文件大小,
- * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
- * 那么删除40%最近没有被使用的文件
- */
- private boolean removeCache(String dirPath) {
- File dir = new File(dirPath);
- File[] files = dir.listFiles();
- if (files == null) {
- return true;
- }
- if (!android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED)) {
- return false;
- }
- int dirSize = 0;
- for (int i = 0; i < files.length; i++) {
- if (files[i].getName().contains(lASTPATHNAME)) {
- dirSize += files[i].length();
- }
- }
- if (dirSize > CACHESIZE * MB
- || SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
- 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(lASTPATHNAME)) {
- files[i].delete();
- }
- }
- }
- if (caluateSDCardFreeSpance() <= CACHESIZE) {
- return false;
- }
- return true;
- }
- /**
- * 获取缓存目录
- *
- * @return : 目录
- */
- private String getDirectory() {
- return getSDCardPath() + "/" + IMAGECACHE;
- }
- /**
- * 将url转换成文件名
- *
- * @param url
- * : 地址
- * @return : 文件名
- */
- private String convertUrlToFileName(final String url) {
- String[] strs = url.split("/");
- return strs[strs.length - 1] + lASTPATHNAME;
- }
- /**
- * 计算sdCard上的空闲空间
- *
- * @return : 大小
- */
- @SuppressLint("NewApi")
- private int caluateSDCardFreeSpance() {
- int freespance = 0;
- StatFs start = new StatFs(Environment.getExternalStorageDirectory()
- .getPath());
- long blocksize = start.getBlockSizeLong();
- long availableBlocks = start.getAvailableBlocksLong();
- freespance = Integer.parseInt(blocksize * availableBlocks + "");
- return freespance;
- }
- /** 修改文件的最后修改时间 **/
- public void updateFileTime(String path) {
- File file = new File(path);
- long lastTime = System.currentTimeMillis();
- file.setLastModified(lastTime);
- }
- /** 根据文件的最后修改时间进行排序 **/
- 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;
- }
- }
- }
- }
(3)http缓存
- package com.zengtao.tools;
- import java.io.FilterInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.HttpStatus;
- import org.apache.http.client.HttpClient;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.impl.client.DefaultHttpClient;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.util.Log;
- /**
- * 网络缓存
- *
- * @author zengtao 2015年4月27日 下午3:32:34
- */
- public class HttpCache {
- private static final String LOG_TAG = "ImageGetFromHttp";
- public static Bitmap downloadBitmap(String url) {
- final HttpClient client = new DefaultHttpClient();
- final HttpGet getRequest = new HttpGet(url);
- try {
- HttpResponse response = client.execute(getRequest);
- final int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- Log.w(LOG_TAG, "Error " + statusCode
- + " while retrieving bitmap from " + url);
- return null;
- }
- final HttpEntity entity = response.getEntity();
- if (entity != null) {
- InputStream inputStream = null;
- try {
- inputStream = entity.getContent();
- FilterInputStream fit = new FlushedInputStream(inputStream);
- return BitmapFactory.decodeStream(fit);
- } finally {
- if (inputStream != null) {
- inputStream.close();
- inputStream = null;
- }
- entity.consumeContent();
- }
- }
- } catch (IOException e) {
- getRequest.abort();
- Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
- } catch (IllegalStateException e) {
- getRequest.abort();
- Log.w(LOG_TAG, "Incorrect URL: " + url);
- } catch (Exception e) {
- getRequest.abort();
- Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
- } finally {
- client.getConnectionManager().shutdown();
- }
- return null;
- }
- /**
- * InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理skip方法来解决这个bug
- * BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我
- * 们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据
- * ,主要原理就是检查是否到文件末端,告诉http类是否继续。
- *
- * @author zengtao 2015年4月27日 下午6:33:17
- */
- static class FlushedInputStream extends FilterInputStream {
- public FlushedInputStream(InputStream inputStream) {
- super(inputStream);
- }
- @Override
- public long skip(long n) throws IOException {
- long totalBytesSkipped = 0L;
- while (totalBytesSkipped < n) {
- long bytesSkipped = in.skip(n - totalBytesSkipped);
- if (bytesSkipped == 0L) {
- int b = read();
- if (b < 0) {
- break; // we reached EOF
- } else {
- bytesSkipped = 1; // we read one byte
- }
- }
- totalBytesSkipped += bytesSkipped;
- }
- return totalBytesSkipped;
- }
- }
- }
(4)主函数中调用
- @SuppressLint("HandlerLeak")
- private Handler handler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.arg1 == 0x1) {
- if (msg.obj != null) {
- image.setImageBitmap((Bitmap) msg.obj);
- }
- }
- };
- };
- class MyThread extends Thread {
- @Override
- public void run() {
- bitmap = getBitmap("http://a.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d16f5022b7ef1deb48f8d5464e3.jpg");
- Message message = new Message();
- message.arg1 = 0x1;
- message.obj = bitmap;
- handler.sendMessage(message);
- }
- }
- /*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
- public Bitmap getBitmap(String url) {
- // 1.从内存缓存中获取图片
- Bitmap resultBitmap = memoryCache.getBitmap(url);
- if (resultBitmap == null) {
- // 2.文件缓存中获取
- resultBitmap = fileCache.getBitmap(url);
- if (resultBitmap == null) {
- // 3.从网络获取
- resultBitmap = HttpCache.downloadBitmap(url);
- if (resultBitmap != null) {
- fileCache.saveBitmap(url, resultBitmap);
- memoryCache.saveBitmap(url, resultBitmap);
- System.out.println("3.网络缓存中获取图片");
- }
- } else {
- // 添加到内存缓存
- memoryCache.saveBitmap(url, resultBitmap);
- System.out.println("2.文件缓存中获取图片");
- }
- } else {
- System.out.println("1.内存缓存中获取图片");
- }
- return resultBitmap;
- }
4.总结
以上就完成了一套缓存的设计,值得注意的是,当去网络获取图片的时候,图片过于庞大,一定要做去异步线程中获取图片,或者做本地缓存,这样不会让用户感觉自己的app卡死,是的用户体验效果更加。

浙公网安备 33010602011771号