本文主要介绍三级缓存的原理解析与实现方式。以前一直觉得三级缓存图片加载是一个很难理解的东西,但是自己看了一下午再试着写了一遍之后感觉还是只要沉下心思考还时很容易熟悉掌握的。
所谓三级缓存:首先是内存-文件(外存)-网络三级缓存机制。
首先:
框架需要一个接入方法NGImageloadHelper.java:
/** * 图片加载框架使用帮助类 * Created by nangua on 2016/7/8. */ public class NGImageloadHelper { /** * 处理图片 * @param view * @param url */ public static void displayImage(ImageView view, String url) { NGDownloadImage.getInstance().addTask(url, view); NGDownloadImage.getInstance().doTask(); } }然后,使用具体的缓存实现类NGDownloadImage:
首先判断传入的url对应图片是否在内外存中,如果不在,则添加进线程池的自定义任务队列中,这里传入的任务是自定义的实现Callble接口的任务TaskWithResult,在带回调参数的执行方法call中执行一个自定义的handler----TaskHandler,该TaskHandler的handlerMessage方法内部根据传入的图片类型判断,执行相应的下载方法(通过HttpUrlConnection实现)并移除taskmap中对应的图片任务。
/** * 图片加载类 * Created by nangua on 2016/7/8. */ public class NGDownloadImage { private ExecutorService executorService; //线程池服务 private NGImageMemoryCache imageMemoryCache; private NGImageFileCache imageFileCache; private NGDownloadImageMode downloadImageMode; //图片实例 private Map内存缓存实现:, View> taskMap; private static NGDownloadImage instance; //自身私有化实例 private int POOL_SIZE = 5;//线程池自定义大小 private NGDownloadImage() { final int cpuNums = Runtime.getRuntime().availableProcessors();//cpu数 executorService = Executors.newFixedThreadPool(cpuNums * POOL_SIZE); imageMemoryCache = new NGImageMemoryCache(); imageFileCache = new NGImageFileCache(); downloadImageMode = new NGDownloadImageMode(); taskMap = new HashMap<>(); } //获得唯一实例 public static synchronized NGDownloadImage getInstance() { if (instance == null) { instance = new NGDownloadImage(); } return instance; } /** * 添加任务 * * @param url * @param img */ public void addTask(String url, ImageView img) { addTask(null, url, img, null); } public void addTask(Object parent, String url, View img, NGImageCallback callback) { if (img == null) { return; } if (TextUtils.isEmpty(url)) { return; } if (callback != null) { downloadImageMode = new NGDownloadImageMode(); downloadImageMode.setCallback(callback); downloadImageMode.setParent(parent); downloadImageMode.setImgUrl(url); img.setTag(downloadImageMode); } else { img.setTag(url); } //生成Bitmap final Bitmap bitmap = imageMemoryCache.getBitmapFromCache(url); //如果缓存里有 if (bitmap != null) { //如果有实现的回调接口,则用回调接口加载图片 if (callback != null) { callback.imageLoaded(parent, img, bitmap, downloadImageMode); } else { //如果没有,则直接设置该图片为bitmap if (img instanceof ImageView) ((ImageView) img).setImageBitmap(bitmap); } } else { //如果缓存没有这个图片 if (taskMap != null) { //添加到任务集合里去 synchronized (taskMap) { final String mapKey = Integer.toString(img.hashCode()); if (!taskMap.containsKey(mapKey)) { taskMap.put(mapKey, img); } } } } } public void doTask() { if (taskMap == null) { return; } else { synchronized (taskMap) { Collection collection = taskMap.values(); for (View view : collection) { if (view != null) { Object object = view.getTag(); String url = ""; if (object instanceof NGDownloadImageMode) { url = ((NGDownloadImageMode) object).getImgUrl(); } else { url = (String) object; } if (!TextUtils.isEmpty(url)) { loadImage(url, view); } } } } } } private void loadImage(final String url, final View img) { loadImage(url, img, null); } private void loadImage(final String url, final View img, NGImageCallback callback) { executorService.submit(new TaskWithResult(new TaskHandler(url, img, callback), url)); } private class TaskWithResult implements Callable { private String url; private Handler handler; public TaskWithResult(Handler handler, String url) { this.url = url; this.handler = handler; } @Override public String call() throws Exception { // TODO Auto-generated method stub final Message message = handler.obtainMessage(0, getBitmap(url)); handler.sendMessage(message); return url; } } private class TaskHandler extends Handler { private String url; private View img; private NGImageCallback callback; public TaskHandler(String url, View img, NGImageCallback callback) { this.url = url; this.img = img; this.callback = callback; } @Override public void handleMessage(Message msg) { final Object object = img.getTag(); if (object instanceof NGDownloadImageMode) { final NGDownloadImageMode imageMode = (NGDownloadImageMode) object; imageMode.getCallback().imageLoaded(imageMode.getParent(), img, (Bitmap) msg.obj, imageMode); if (taskMap != null) { taskMap.remove(Integer.toString(img.hashCode())); } } else if (object instanceof String) { if (callback != null) { callback.imageLoaded(null, img, (Bitmap) msg.obj, url); } else { if (object.equals(url) && msg.obj != null) { final Bitmap bitmap = (Bitmap) msg.obj; if (bitmap != null) { if (img instanceof ImageView) { ((ImageView) img).setImageBitmap(bitmap); } } } } if (taskMap != null) { taskMap.remove(Integer.toString(img.hashCode())); } } } } /** * @param url * @return Bitmap */ public Bitmap getBitmap(String url) { Bitmap bitmap = imageMemoryCache.getBitmapFromCache(url); if (bitmap == null) { bitmap = imageFileCache.getImage(url); if (bitmap == null) { bitmap = getBitmapFromUrl(url); if (bitmap != null) { imageMemoryCache.addBitmapToCache(url, bitmap); imageFileCache.saveBmpToSd(url,bitmap); } } else { imageMemoryCache.addBitmapToCache(url, bitmap); } } return bitmap; } public static Bitmap getBitmapFromUrl(String path) { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); if (conn.getResponseCode() == 200) { InputStream inputStream = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(inputStream); return bitmap; } } catch (Exception e) { e.printStackTrace(); } return null; } public interface NGImageCallback { public void imageLoaded(Object parent, View img, Bitmap imageBitmap, NGDownloadImageMode callBackTag); public void imageLoaded(Object parent, View img, Bitmap imageBitmap, String imageUrl); } }
通过强&软引用配合使用实现内存缓存机制,强引用使用HashMap实现,软引用使用线程安全的 ConcurrentHashMap实现(实现原理是锁分离技术,使用多个锁来控制对hash表的不同部分的修改,内部使用段(Segment)来表示这些不同的部分,每个段是一个小的hashtable,可并发运行),淘汰算法如下:
/**
* 初始化
* 淘汰最老的键
*/
protected NGImageMemoryCache() {
//使用LinkedHashMap保证有序读取
hashMap = new LinkedHashMap(MAX_CACHE_CAPACITY, 0.75f, true) {
//移除hashmap中最老的键值
@Override
protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
if (size() > MAX_CACHE_CAPACITY) {
mSoftBitmapCache.put(eldest.getKey(), new SoftReference(eldest.getValue()));
return true; //返回true则移除最老的键值
} else {
return false;
}
}
};
}
类似于LRU算法实现?(逃~)
其他增删进入内存方法就不贴上来了~
外存缓存实现:
很简单这里就只讲一下思路了,把图片文件存储到本地指定文件夹中,注意进行剩余容量判断及时清除最老的图片就行了。
*********************************************************************分隔线************************************************************************
近段时间换了一家公司实习,不过创业公司也有创业公司の可怕啊= =没有下班の概念。。。希望能尽快参与到新项目开发,学到更多的东西。