16.手写图片加载框架ImageLoader
概述
第三方开源的图片框架很多,这里自己去写一个的目的是通过这样一个写的过程,拓展自己对架构设计的理解,包括设计模式,线程,策略,缓存等等。另外大型的框架例如Glide,代码很完善,扩展性很高,但是阅读起来有难度,而实际上,这些框架底层实现原理都是类似的,所以通过构建一个简单框架的过程更加有助于对其原理的理解,算是为阅读复杂的第三方源码打下一个基础。
github地址:https://github.com/renzhenming/ImageLoader.git
今天的框架要实现一下的功能:
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中获取,有缓存则直接展示然后再去加载网络图片,加载网络图片的时候又涉及到加载策略的选择,根据不同策略进行不同的加载。
关键代码
1.配置管理
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;
}
2.SimpleImageLoader
以单例形式构建的交互类,持有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;
}
}
3.BitmapRequest请求对象
一个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<BitmapRequest>{
/**
* 展示配置
*/
private DisplayConfig disPlayConfig;
/**
* 加载策略
*/
private LoadPolicy loadPolicy = SimpleImageLoader.getInstance().getConfig().getLoadPolicy();
/**
* 序列号,用于顺序比较
*/
private int serialNum;
/**
* 持有ImageView的软引用
*/
private SoftReference<ImageView> 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>(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<ImageView> 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;
}
}
4.分发器实现原理
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 BlockingQueue<BitmapRequest> blockingQueue;
public RequestDispatcher(BlockingQueue<BitmapRequest> 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;
}
}
5.加载器实现原理
目前该框架支持网络图片和本地图片加载,不同的加载器根据不同的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);
}
6.本地缓存
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 LruCache<String,Bitmap> mLruCache;
public MemoryCache(){
int maxSize = (int) (Runtime.getRuntime().maxMemory()/1024/8);
mLruCache = new LruCache<String,Bitmap>(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());
}
}
7.硬盘缓存
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();
}
}
}