本文共 28821 字,大约阅读时间需要 96 分钟。
第三方开源的图片框架很多,这里自己去写一个的目的是通过这样一个写的过程,拓展自己对架构设计的理解,包括设计模式,线程,策略,缓存等等。另外大型的框架例如Glide,代码很完善,扩展性很高,但是阅读起来有难度,而实际上,这些框架底层实现原理都是类似的,所以通过构建一个简单框架的过程更加有助于对其原理的理解,算是为阅读复杂的第三方源码打下一个基础。
github地址:
今天的框架要实现一下的功能:
1.根据用户需求可以灵活配置(建造者模式)
2.支持高并发,图片加载的优先级 3.支持可以选择不同的加载策略,对加载策略进行扩展 4.二级缓存 加载图片时内存中已经加载了,则从内存中加载,不存在去外置卡中5.加载,外置还不存在则从网络下载 6.并对缓存策略可以扩展 7.支持从加载过程中显示默认加载图片 8.支持加载失败时 显示默认错误图片 9.图片显示自适应。从网络加载下来的图片经最佳比例压缩后显示不能失真变形 10.支持请求转发,下载用到的模式:
1.生产者 消费者模式 2.建造者模式 3.单例模式 4.模板方法模式 5.策略模式用到的知识点
1.内存缓存 LruCache技术 2.硬盘缓存技术DiskLruCache技术 3.图片下载时请求转发如上图,首先使用Builder设计模式构建ImageLoaderConfig,这个类处理图片加载框架的全局配置信息,包括加载策略,缓存策略,线程数,以及加载中一些图片的配置,封装成了DisplayConfig对象;SimpleImageLoader是对外暴露的主类,它持有配置对象的引用,所以它可以调用所以图片加载中所涉及的配置,然后将这些配置封装成BitmapRequest对象,一个BitmapRequest对象对应一次网络请求,在SimpleImageLoader初始化的同时还会初始化全局的请求队列RequestQueue,BitmapRequest对象会被加入队列中,这时候分发器开始工作,将BitmapRequest按照一定协议分发给不同的加载器,加载器拿到请求后先从缓存BitmapCache中获取,有缓存则直接展示然后再去加载网络图片,加载网络图片的时候又涉及到加载策略的选择,根据不同策略进行不同的加载。
ImageLoaderConfig ,配置管理类,我们把一些关键的配置信息单独封装起来,以Builder模式构建,用户可以自由的配置自己需要的,同时提高扩展性,这正是Builder设计模式的优点.ImageLoaderConfig 负责处理整个框架的配置信息,目前包括缓存策略,加载策略,加载中显示的占位图,加载失败展示的占位图等等
package com.rzm.imageloader.config;import com.rzm.imageloader.cache.BitmapCache;import com.rzm.imageloader.policy.LoadPolicy;/** * Author:renzhenming * Time:2018/6/13 7:21 * Description:This is ImageLoaderConfig * 以建造者模式实现,管理ImageLoader配置信息 */public class ImageLoaderConfig { /** * 图片显示配置 TODO 初始化 */ private DisplayConfig displayConfig; /** * 缓存策略 */ private BitmapCache bitmapCache; /** * 加载策略 */ private LoadPolicy loadPolicy; /** * 默认线程数 */ private int threadCount = Runtime.getRuntime().availableProcessors(); private ImageLoaderConfig(){} /** * 建造者模式 */ public static class Builder{ /** * Builder持有外部类的引用,在new的时候创建出来 */ private ImageLoaderConfig config; public Builder(){ config = new ImageLoaderConfig(); } /** * 设置缓存策略 * @param bitmapCache * @return */ public Builder setCachePolicy(BitmapCache bitmapCache){ config.bitmapCache = bitmapCache; return this; } /** * 设置加载策略 * @param loadPolicy * @return */ public Builder setLoadPolicy(LoadPolicy loadPolicy){ config.loadPolicy = loadPolicy; return this; } /** * 设置线程数 * @param threadCount * @return */ public Builder setThreadCount(int threadCount){ config.threadCount = threadCount; return this; } /** * 设置加载过程中的图片 * @param resId * @return */ public Builder setLoadingImage(int resId){ if (config.displayConfig == null){ throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null"); } config.displayConfig.loadingImage = resId; return this; } /** * 设置加载失败显示的图片 * @param resId * @return */ public Builder setErrorImage(int resId){ if (config.displayConfig == null){ throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null"); } config.displayConfig.errorImage = resId; return this; } /** * 构建 * @return */ public ImageLoaderConfig build(){ return config; } } public DisplayConfig getDisplayConfig() { return displayConfig; } public BitmapCache getBitmapCache() { return bitmapCache; } public LoadPolicy getLoadPolicy() { return loadPolicy; } public int getThreadCount() { return threadCount; }}
package com.rzm.commonlibrary.general.imageloader.config;/** * Author:renzhenming * Time:2018/6/13 7:24 * Description:This is DisplayConfig * 图片显示配置类,单独拿出来作为一个单独类有利于扩展,仿Glide */public class DisplayConfig { /** * 加载过程中的占位图片 */ public int loadingImage = -1; /** * 加载失败显示的图片 */ public int errorImage = -1;}
以单例形式构建的交互类,持有ImageLoaderConfig 配置引用,负责将每一个网络请求封装成BitmapRequest对象,SimpleImageLoader初始化的时候会构建出一个全局的阻塞式队列,BitmapRequest会被加入这个队列中,此时分发器开始工作,分发器的构建启发于Android消息队列中的looper,负责将每个Request请求从队列中取出交给负责处理这个请求的加载器
package com.rzm.commonlibrary.general.imageloader.loader;import android.graphics.Bitmap;import android.widget.ImageView;import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;import com.rzm.commonlibrary.general.imageloader.config.ImageLoaderConfig;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;import com.rzm.commonlibrary.general.imageloader.request.RequestQueue;/** * Author:renzhenming * Time:2018/6/13 7:22 * Description:This is SimpleImageLoader */public class SimpleImageLoader { /** * 持有配置信息对象的引用 */ private ImageLoaderConfig config; private static volatile SimpleImageLoader instance; /** * 请求队列 */ private RequestQueue requestQueue; private SimpleImageLoader(){} private SimpleImageLoader(ImageLoaderConfig config){ this.config = config; //初始化请求队列 requestQueue = new RequestQueue(config.getDispatcherCount()); //开启请求队列 requestQueue.start(); } /** * 用于在Application中初始化ImageLoader 设置配置信息 * 必须,否则调用空参getInstance()会抛异常 * @param config * @return */ public static SimpleImageLoader init(ImageLoaderConfig config){ if (instance == null){ synchronized (SimpleImageLoader.class){ if (instance == null){ instance = new SimpleImageLoader(config); } } } return instance; } /** * 用于在代码中获取单例对象 * @return */ public static SimpleImageLoader getInstance(){ if (instance == null){ throw new UnsupportedOperationException("SimpleImageLoader haven't been init with ImageLoaderConfig,call init(ImageLoaderConfig config) in your application"); } return instance; } /** * 显示图片 * @param imageView * @param url */ public void display(ImageView imageView,String url){ display(imageView,url,null,null); } /** * 显示图片 * @param imageView * @param url */ public void display(ImageView imageView,String url,DisplayConfig displayConfig){ display(imageView,url,displayConfig,null); } /** * 显示图片 * @param imageView * @param url */ public void display(ImageView imageView,String url,ImageListener listener){ display(imageView,url,null,listener); } /** * 显示图片,用于针对特殊图片配置特殊的配置信息 * @param imageView * @param url * @param displayConfig * @param listener */ public void display(ImageView imageView,String url,DisplayConfig displayConfig,ImageListener listener){ if (imageView == null){ throw new NullPointerException("ImageView cannot be null"); } //封装成一个请求对象 BitmapRequest request= new BitmapRequest(imageView,url,displayConfig,listener); //加入请求队列 requestQueue.addRequest(request); } /** * 监听图片,设置后期处理,仿Glide */ public static interface ImageListener{ void onComplete(ImageView imageView, Bitmap bitmap,String url); } /** * 获取全局配置 * @return */ public ImageLoaderConfig getConfig() { return config; }}
一个BitmapRequest对象中封装了这次请求的所有相关信息,包括这个请求的图片显示逻辑的配置DisplayConfig,当前全局的加载策略LoadPolicy,请求图片的网络地址和这个地址需要展示的控件对象ImageView,另外我对BitmapRequest对象重写了hashCode和equals方法,实现了Comparable接口,以实现这个对象的比较逻辑,为加载策略服务
package com.rzm.commonlibrary.general.imageloader.request;import android.widget.ImageView;import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;import com.rzm.commonlibrary.general.imageloader.loader.SimpleImageLoader;import com.rzm.commonlibrary.general.imageloader.policy.LoadPolicy;import com.rzm.commonlibrary.general.imageloader.utils.Md5Util;import java.lang.ref.SoftReference;/** * Author:renzhenming * Time:2018/6/13 7:23 * Description:This is BitmapRequest */public class BitmapRequest implements Comparable{ /** * 展示配置 */ private DisplayConfig disPlayConfig; /** * 加载策略 */ private LoadPolicy loadPolicy = SimpleImageLoader.getInstance().getConfig().getLoadPolicy(); /** * 序列号,用于顺序比较 */ private int serialNum; /** * 持有ImageView的软引用 */ private SoftReference imageViewSoftReference; /** * 图片路径 */ private String imageUrl; /** * 图片路径的md5值 */ private String imageUrlMd5; /** * 下载完成的监听 */ private SimpleImageLoader.ImageListener imageListener; public BitmapRequest() { } public BitmapRequest(ImageView imageView, String imageUrl) { this(imageView,imageUrl,null,null); } public BitmapRequest(ImageView imageView,String imageUrl,DisplayConfig displayConfig) { this(imageView,imageUrl,displayConfig,null); } public BitmapRequest(ImageView imageView,String imageUrl,SimpleImageLoader.ImageListener imageListener) { this(imageView,imageUrl,null,imageListener); } public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig, SimpleImageLoader.ImageListener imageListener){ this.imageViewSoftReference = new SoftReference (imageView); if (imageUrl != null) { imageView.setTag(imageUrl); imageUrlMd5 = Md5Util.toMD5(imageUrl); } this.imageUrl = imageUrl; if (displayConfig != null){ this.disPlayConfig = displayConfig; } if (imageListener != null) { this.imageListener = imageListener; } } /** * 请求的先后顺序是根据加载的策略进行的,不同的策略比较的条件也不同,所以 * 这里要把比较的逻辑交割具体的策略去做,策略有多种,所以通过接口调用,增强扩展性 * @return */ @Override public int compareTo(BitmapRequest o) { return loadPolicy.compareTo(o,this); } public int getSerialNum() { return serialNum; } public void setSerialNum(int serialNum) { this.serialNum = serialNum; } /** * 获取这个请求对应的ImageView * @return */ public ImageView getImageView(){ if (imageViewSoftReference == null) return null; return imageViewSoftReference.get(); } public DisplayConfig getDisPlayConfig() { return disPlayConfig; } public LoadPolicy getLoadPolicy() { return loadPolicy; } public SoftReference getImageViewSoftReference() { return imageViewSoftReference; } public String getImageUrl() { return imageUrl; } public String getImageUrlMd5() { return imageUrlMd5; } public SimpleImageLoader.ImageListener getImageListener() { return imageListener; } /** * BitmapRequest会被加入请求队列中,在队列中有需要做判断当前请求是否存在 * 那么就涉及到这个对象的比较,所以需要重写hashCode和equals方法 */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BitmapRequest request = (BitmapRequest) o; return serialNum == request.serialNum && loadPolicy.equals(request.loadPolicy); } @Override public int hashCode() { int result = loadPolicy != null ? loadPolicy.hashCode():0; result = 66*result+serialNum; return result; }}
RequestDispatcher是一个继承了Thread的线程对象,队列创建后会根据配置创建出指定数量的分发器,当队列中有请求对象后就从队列中取出对象交给加载器,根据一定的协议选择合适的加载器进行网络请求,当队列中没有对象时,会进入休眠状态
package com.rzm.commonlibrary.general.imageloader.request;import android.text.TextUtils;import android.util.Log;import com.rzm.commonlibrary.general.imageloader.loader.Loader;import com.rzm.commonlibrary.general.imageloader.loader.LoaderManager;import java.util.concurrent.BlockingQueue;/** * Author:renzhenming * Time:2018/6/13 7:24 * Description:This is RequestDispatcher */public class RequestDispatcher extends Thread{ /** * 从队列中转发请求需要持有队列的引用 */ private BlockingQueueblockingQueue; public RequestDispatcher(BlockingQueue blockingQueue) { this.blockingQueue = blockingQueue; } /** * 阻塞式队列,转发器开启,从队列中取请求队列,如果没有则会阻塞当前线程,所以这里 * 是在子线程开启的 */ @Override public void run() { while(!isInterrupted()){ try { BitmapRequest request = blockingQueue.take(); //处理请求对象,交给loader String schema = parseSchema(request.getImageUrl()); //获取加载器 Loader loader = LoaderManager.getInstance().getLoader(schema); if (loader == null){ Log.d("TAG",request.getImageUrl() + "没有找到对应的加载器"); return; } loader.load(request); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 根据图片url判断加载类型 * @param imageUrl * @return */ private String parseSchema(String imageUrl) { if (TextUtils.isEmpty(imageUrl)){ return null; } if (imageUrl.contains("://")){ //形如 http://xxx 或者file://xxx,这样截取后 //可以获得http file等前缀,根据这个前缀获取相应 //的加载器 return imageUrl.split("://")[0]; }else{ Log.d("TAG","不持支的图片类型"); } return null; }}
目前该框架支持网络图片和本地图片加载,不同的加载器根据不同的url进行选择,为了提高扩展性,设置接口和抽象类的实现方式,下面是顶层接口
package com.rzm.commonlibrary.general.imageloader.loader;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;/** * Author:renzhenming * Time:2018/6/13 7:21 * Description:This is Loader */public interface Loader { /** * 加载图片 * @param request */ void load(BitmapRequest request);}
网络加载器和本地加载器实现逻辑有所不同,但是有一些公共的操作存在,比如加载前显示加载中占位图,加载失败显示失败图片等等,这些操作可以放在一个公共的基类中实现,所以这里创建了一个抽象类作为上层类处理公共逻辑
package com.rzm.commonlibrary.general.imageloader.loader;import android.graphics.Bitmap;import android.widget.ImageView;import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;import java.util.concurrent.atomic.AtomicInteger;/** * Author:renzhenming * Time:2018/6/13 7:22 * Description:This is AbstractLoader * 加载器策略不同,则不同的加载器实现方式不同,但是他们有相同的操作,比如显示 * 占位图等,所以这些相同操作在抽象一层出来 */public abstract class AbstractLoader implements Loader { private static final String TAG = "AbstractLoader"; private AtomicInteger integer = new AtomicInteger(0); /** * 加载器加载图片的逻辑是先缓存后网络,所以需要持有缓存对象的引用 */ private BitmapCache bitmapCache = SimpleImageLoader.getInstance().getConfig().getBitmapCache(); /** * 同样因为要处理显示时的逻辑,所以需要持有显示配置对象的引用 */ private DisplayConfig displayConfig = SimpleImageLoader.getInstance().getConfig().getDisplayConfig(); @Override public void load(BitmapRequest request) { //从缓存中获取Bitmap Bitmap bitmap= null; if (bitmapCache != null) { bitmap = bitmapCache.get(request); } if (bitmap == null){ //显示加载中图片 showLoadingImg(request); //开始加载网络图,加载的逻辑不同加载器有所不同,所以交给各自 //加载器实现,抽象 bitmap = onLoad(request); if (bitmap == null){ //加载失败重试三次 while(integer.incrementAndGet() <=3){ bitmap = onLoad(request); if (bitmap != null){ break; } } integer.set(0); } if (bitmap == null){ } //加入缓存 if (bitmapCache != null && bitmap != null) cacheBitmap(request,bitmap); }else{ //有缓存 } deliveryToUIThread(request,bitmap); } public abstract Bitmap onLoad(BitmapRequest request); protected void deliveryToUIThread(final BitmapRequest request, final Bitmap bitmap) { ImageView imageView = request.getImageView(); if(imageView!=null) { imageView.post(new Runnable() { @Override public void run() { updateImageView(request, bitmap); } }); } } private void updateImageView(final BitmapRequest request, final Bitmap bitmap) { ImageView imageView = request.getImageView(); //加载正常 防止图片错位 if(bitmap != null && imageView.getTag().equals(request.getImageUrl())){ imageView.setImageBitmap(bitmap); } //有可能加载失败 if(bitmap == null && displayConfig!=null&&displayConfig.errorImage!=-1){ imageView.setImageResource(displayConfig.errorImage); } //监听 //回调 给圆角图片 特殊图片进行扩展 if(request.getImageListener() != null){ request.getImageListener().onComplete(imageView, bitmap, request.getImageUrl()); } } /** * 缓存图片 * @param request * @param bitmap */ private void cacheBitmap(BitmapRequest request, Bitmap bitmap) { if (request != null && bitmap != null){ synchronized (AbstractLoader.class){ bitmapCache.put(request,bitmap); } } } /** * 显示加载中占位图,需要判断用户有没有配置 * @param request */ private void showLoadingImg(BitmapRequest request) { if (hasLoadingPlaceHolder()){ final ImageView imageView = request.getImageView(); if (imageView != null){ imageView.post(new Runnable() { @Override public void run() { imageView.setImageResource(displayConfig.loadingImage); } }); } } } /** * 是否设置了加载中图片 * @return */ private boolean hasLoadingPlaceHolder() { return displayConfig != null && displayConfig.loadingImage > 0; }}
接下来是具体的加载器实现
package com.rzm.commonlibrary.general.imageloader.loader;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.text.TextUtils;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;import java.io.BufferedInputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;/** * Author:renzhenming * Time:2018/6/13 7:22 * Description:This is UrlLoader * 网络图片加载器 */public class UrlLoader extends AbstractLoader { @Override public Bitmap onLoad(BitmapRequest request) { try { String imageUrl = request.getImageUrl(); if (TextUtils.isEmpty(imageUrl)){ return null; } URL url = new URL(imageUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); if (conn.getResponseCode() != 200){ return null; } InputStream inputStream = conn.getInputStream(); //转化成BufferedInputStream final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); //标记一下,reset后会重置到这个位置 bufferedInputStream.mark(inputStream.available()); BitmapDecoder decoder = new BitmapDecoder() { @Override public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) { //第一次读取,因为设置了inJustDecodeBounds为true,所以,这里decodeStream之后,会将宽高 //信息存储在options中;第二次读取,因为设置了inJustDecodeBounds为false.所以会将流全部读取 Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream,null,options); if (options.inJustDecodeBounds){ //表示时第一次执行,此时只是为了获取Bounds try { //第一次读取图片宽高信息,读完之后,要为第二次读取做准备,将流重置 bufferedInputStream.reset(); } catch (IOException e) { e.printStackTrace(); } }else{ try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return bitmap; } }; //传入控件的宽高,设置图片适应控件 return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()), ImageViewHelper.getImageViewHeight(request.getImageView())); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }}
package com.rzm.commonlibrary.general.imageloader.loader;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;import java.io.File;/** * Author:renzhenming * Time:2018/6/13 7:22 * Description:This is LocalLoader * 本地图片加载器 */public class LocalLoader extends AbstractLoader { @Override public Bitmap onLoad(BitmapRequest request) { //得到本地图片的路径 final String path = Uri.parse(request.getImageUrl()).getPath(); File file = new File(path); if (!file.exists() || !file.isFile()){ return null; } BitmapDecoder decoder = new BitmapDecoder() { @Override public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) { return BitmapFactory.decodeFile(path,options); } }; return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()), ImageViewHelper.getImageViewHeight(request.getImageView())); }}
package com.rzm.commonlibrary.general.imageloader.utils;import android.graphics.Bitmap;import android.graphics.BitmapFactory;/** * 图片解码器 */public abstract class BitmapDecoder { /** * 压缩图片 * @param width imageView的宽度 * @param height imageView的高度 * @return */ public Bitmap decodeBitmap(int width,int height){ BitmapFactory.Options options = new BitmapFactory.Options(); //设置为true 只读取图片的宽高,不需要将整个图片都加载到内存 options.inJustDecodeBounds = true; decodeBitmapWithOptions(options); //经过上面一次操作,此时options中已经有了宽高信息 calculateSampleSizeWithOptions(options,width,height); //第二次就可以得到缩放后的bitmap了 return decodeBitmapWithOptions(options); } /** * 将图片宽高和控件宽高进行比较,得到缩放值,信息仍然存储在options中 * @param options * @param viewWidth * @param viewHeight */ private void calculateSampleSizeWithOptions(BitmapFactory.Options options,int viewWidth,int viewHeight) { //计算缩放比例 //图片的原始宽高 int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; //当图片的宽高大于控件的宽高时才需要压缩 if (width > viewWidth || height > viewHeight){ //计算出宽高的缩放比例 int widthRatio = Math.round((float) width/(float)viewWidth); int heightRatio = Math.round((float)height/(float)viewHeight); //取宽高缩放比较大的值为图片的缩放比 inSampleSize = Math.max(widthRatio,heightRatio); } //设置到options中,options保存的是配置信息 //当inSampleSize为2,图片的宽高会缩放为原来的1/2 options.inSampleSize = inSampleSize; //每个像素2个字节 options.inPreferredConfig = Bitmap.Config.RGB_565; //宽高已经计算出来了,inJustDecodeBounds值可以复位了 options.inJustDecodeBounds = false; //当系统内存不足时.可以回收bitmap options.inPurgeable = true; options.inInputShareable = true; } /** * 将流的处理通过抽象方法暴露出来,降低解码器和外部的耦合 * @param options */ public abstract Bitmap decodeBitmapWithOptions(BitmapFactory.Options options);}
package com.rzm.commonlibrary.general.imageloader.cache;import android.graphics.Bitmap;import android.util.LruCache;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;/** * Author:renzhenming * Time:2018/6/13 7:20 * Description:This is MemoryCache */public class MemoryCache implements BitmapCache{ private LruCachemLruCache; public MemoryCache(){ int maxSize = (int) (Runtime.getRuntime().maxMemory()/1024/8); mLruCache = new LruCache (maxSize){ @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight(); } }; } @Override public Bitmap get(BitmapRequest request) { if (mLruCache == null) return null; return mLruCache.get(request.getImageUrlMd5()); } @Override public void put(BitmapRequest request, Bitmap bitmap) { if (mLruCache == null) return; mLruCache.put(request.getImageUrlMd5(),bitmap); } @Override public void remove(BitmapRequest request) { if (mLruCache == null) return; mLruCache.remove(request.getImageUrlMd5()); }}
package com.rzm.commonlibrary.general.imageloader.disk;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;import com.rzm.commonlibrary.general.imageloader.utils.IOUtil;import java.io.BufferedOutputStream;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;/** * Author:renzhenming * Time:2018/6/13 7:20 * Description:This is DiskCache */public class DiskCache implements BitmapCache { private static volatile DiskCache mDiskCache; //缓存路径 private String mCacheDir = "Image"; //MB private static final int MB = 1024 * 1024; //jackwharton的杰作 private DiskLruCache mDiskLruCache; private DiskCache(Context context) { iniDiskCache(context); } public static DiskCache getInstance(Context context) { if(mDiskCache==null) { synchronized (DiskCache.class) { if(mDiskCache==null) { mDiskCache=new DiskCache(context); } } } return mDiskCache; } private void iniDiskCache(Context context) { //得到缓存的目录 android/data/data/com.xxx/cache/Image File directory=getDiskCache(mCacheDir,context); if(!directory.exists()) { directory.mkdirs(); } try { //最后一个参数 指定缓存容量 mDiskLruCache=DiskLruCache.open(directory,1,1,50*MB); } catch (IOException e) { e.printStackTrace(); } } private File getDiskCache(String mCacheDir, Context context) { //默认缓存路径 return new File(context.getCacheDir(),mCacheDir); //return new File(Environment.getExternalStorageDirectory(),mCacheDir); } @Override public void put(BitmapRequest request, Bitmap bitmap) { if (mDiskLruCache == null) return; DiskLruCache.Editor edtor=null; OutputStream os=null; try { //路径必须是合法字符 edtor=mDiskLruCache.edit(request.getImageUrlMd5()); os=edtor.newOutputStream(0); if(persistBitmap2Disk(bitmap,os)) { edtor.commit(); }else { edtor.abort(); } } catch (IOException e) { e.printStackTrace(); } } private boolean persistBitmap2Disk(Bitmap bitmap, OutputStream os) { BufferedOutputStream bos=new BufferedOutputStream(os); bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos); try { bos.flush(); } catch (IOException e) { e.printStackTrace(); }finally { IOUtil.closeQuietly(bos); } return true; } @Override public Bitmap get(BitmapRequest request) { if (mDiskLruCache == null) return null; try { DiskLruCache.Snapshot snapshot=mDiskLruCache.get(request.getImageUrlMd5()); if(snapshot!=null) { InputStream inputStream=snapshot.getInputStream(0); return BitmapFactory.decodeStream(inputStream); } } catch (IOException e) { e.printStackTrace(); } return null; } @Override public void remove(BitmapRequest request) { if (mDiskLruCache == null) return; try { mDiskLruCache.remove(request.getImageUrlMd5()); } catch (IOException e) { e.printStackTrace(); } }}
转载地址:http://qvgfl.baihongyu.com/