大家在做项目中肯定都会用到一些第三方图片加载库,比如glide、imageload、picasso等,比如glide使用起来非常方便,链式调用代码量又非常少,那为什么还要多此一举再次封装呢?那是因为你没有中途更换图片加载库的情况。假如代码中之前使用的是glide来加载图片,由于调用方式比较简洁,你直接在代码中一把梭,那么有一天你想换成picasso这个库来加载图片,那么恭喜你中奖了,你是不是需要检查很多个类文件一个个的去修改,那么有没有一种方式,不管我们使用的是哪个图片加载库,都不会影响到我们代码中的调用?之前自己封装了一个,不过最近浏览文章的时候发现一个老铁实现的思路更简洁,老铁的文章地址,只不过他是对picasso进行了封装,其实glide也是同理。处于学习的目的自己也跟着主要实现了对Glide的封装。
首先,我们希望封装之后调用起来必须要是足够简洁方便的,所以可以考虑建造者模式(也叫Builder模式),好处是我们可以很明显看出每一项的含义以及一条链调用更加简洁。其次,前面说了必须做到更换库时要足够的灵活,并且不能影响代码中调用处的代码逻辑,所以我们可以使用策略模式。
一般什么时候会优先使用策略模式?当处理同一类问题时,只是具体行为操作有差异时,或者代码中出现迷之缩进的if-else if等大量的判断时,那么用策略模式来处理会是一个很不错的选择。说了半天的废话了,下面开始正文
我们希望最终的调用是这样的,只需要调用loadImage()方法就行
ImageLoadUtil.getInstance().loadImage(this,
GlideImageLoadConfig.builder()
.load(url)
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(imageView)
.build());
所以,我们先定义个抽象策略类,定义不管使用哪种图片加载库都需要实现的行为,这里给出了常用的加载图片以及清除缓存两个行为
public interface BaseImageLoadInterface<T extends BaseImageLoadConfig> {
void loadImage(Context context, T imageLoadConfig);
void clearCache(Context context);
}
前面说了如果后期需要替换图片加载框架,我们不希望再去一个个类的去修改了,所以肯定不能在代码调用处使用此框架去调用自己的一些方法,换句话说比如你使用的是glide,要满足这些的话你就不能在调用处还使用glide去调用,我们应该把glide框架常用的一些方法用自己定义的方法来代替,比如加载地址可以是一个integer类型的resourcesId,也可以是一个bitmap等等,考虑到这些共同的属性在不同的框架中都有体现,所以我们可以先定义一个通用的属性配置基类BaseImageLoadConfig
public class BaseImageLoadConfig {
protected String url;
protected Uri uri;
protected Bitmap bitmap;
protected Drawable drawable;
protected Integer resourcesId;
protected ImageView imageView;
protected int placeholder;
protected int error;
//省略一些set/get方法
}
接着我们就可以分别配置glide或者picasso的一些各自特有的属性
public class GlideImageLoadConfig extends BaseImageLoadConfig {
public static final int DISKCACHESTRATEGY_ALL = 0;
public static final int DISKCACHESTRATEGY_NONE = 1;
public static final int DISKCACHESTRATEGY_DATA = 2;
public static final int DISKCACHESTRATEGY_RESOURCE = 3;
public static final int DISKCACHESTRATEGY_AUTOMATIC = 4;
private int diskCacheStrategy = DISKCACHESTRATEGY_AUTOMATIC;//硬盘缓存策略
private boolean skipMemoryCache;//true表示跳过内存缓存
private GlideImageLoadConfig(Builder builder){
this.url = builder.url;
this.uri = builder.uri;
this.bitmap = builder.bitmap;
this.drawable = builder.drawable;
this.resourcesId = builder.resourcesId;
this.imageView = builder.imageView;
this.placeholder = builder.placeholder;
this.error = builder.error;
this.diskCacheStrategy = builder.diskCacheStrategy;
this.skipMemoryCache = builder.skipMemoryCache;
}
public int getDiskCacheStrategy() {
return diskCacheStrategy;
}
public boolean isSkipMemoryCache() {
return skipMemoryCache;
}
public static Builder builder(){
return new Builder();
}
public static class Builder{
private String url;
private Uri uri;
private Bitmap bitmap;
private Drawable drawable;
private Integer resourcesId;
private ImageView imageView;
private int placeholder;
private int error;
private int diskCacheStrategy;
private boolean skipMemoryCache;
private Builder(){
}
public Builder load(String url){
this.url = url;
return this;
}
public Builder load(Uri uri){
this.uri = uri;
return this;
}
public Builder load(Bitmap bitmap){
this.bitmap = bitmap;
return this;
}
public Builder load(Drawable drawable){
this.drawable = drawable;
return this;
}
public Builder load(Integer resourcesId){
this.resourcesId = resourcesId;
return this;
}
public Builder into(ImageView imageView){
this.imageView = imageView;
return this;
}
public Builder placeholder(int placeholder){
this.placeholder = placeholder;
return this;
}
public Builder error(int error){
this.error = error;
return this;
}
public Builder diskCacheStrategy(int diskCacheStrategy){
this.diskCacheStrategy = diskCacheStrategy;
return this;
}
public Builder skipMemoryCache(boolean skipMemoryCache){
this.skipMemoryCache = skipMemoryCache;
return this;
}
public GlideImageLoadConfig build(){
return new GlideImageLoadConfig(this);
}
}
}
这是针对glide的,如果你后期打算换成picasso框架的话,只需要再写一个picasso具体属性的配置类即可。
该有的属性都有了,那么接下来就该去定义一个具体的实现类了,在实现类中去将用户设置的属性赋值给框架相应的api
public class GlideImageLoadImpl implements BaseImageLoadInterface<GlideImageLoadConfig> {
@Override
public void loadImage(Context context, GlideImageLoadConfig imageLoadConfig) {
if (context == null) throw new NullPointerException("context is required");
if (imageLoadConfig == null) throw new NullPointerException("imageLoadConfig is required");
if (imageLoadConfig.imageView == null) throw new NullPointerException("imageview is required");
GlideRequests glideRequests = GlideApp.with(context);
//load
GlideRequest glideRequest;
if (!TextUtils.isEmpty(imageLoadConfig.url)) {
glideRequest = glideRequests.load(imageLoadConfig.url);
} else if (imageLoadConfig.bitmap != null) {
glideRequest = glideRequests.load(imageLoadConfig.bitmap);
} else if (imageLoadConfig.uri != null) {
glideRequest = glideRequests.load(imageLoadConfig.uri);
} else if (imageLoadConfig.drawable != null) {
glideRequest = glideRequests.load(imageLoadConfig.drawable);
} else if (imageLoadConfig.resourcesId != null && imageLoadConfig.resourcesId != 0) {
glideRequest = glideRequests.load(imageLoadConfig.resourcesId);
} else {
glideRequest = glideRequests.load(imageLoadConfig.resourcesId);
}
//硬盘缓存相关
switch (imageLoadConfig.getDiskCacheStrategy()){
case GlideImageLoadConfig.DISKCACHESTRATEGY_ALL:
glideRequest.diskCacheStrategy(DiskCacheStrategy.ALL);
break;
case GlideImageLoadConfig.DISKCACHESTRATEGY_NONE:
glideRequest.diskCacheStrategy(DiskCacheStrategy.NONE);
break;
case GlideImageLoadConfig.DISKCACHESTRATEGY_DATA:
glideRequest.diskCacheStrategy(DiskCacheStrategy.DATA);
break;
case GlideImageLoadConfig.DISKCACHESTRATEGY_RESOURCE:
glideRequest.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
break;
case GlideImageLoadConfig.DISKCACHESTRATEGY_AUTOMATIC:
glideRequest.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);
break;
}
//是否跳过内存缓存
glideRequest.skipMemoryCache(imageLoadConfig.isSkipMemoryCache());
//占位符
if (imageLoadConfig.placeholder != 0) {
glideRequest.placeholder(imageLoadConfig.placeholder);
}
//错误图片
if (imageLoadConfig.error != 0) {
glideRequest.error(imageLoadConfig.error);
}
//into
glideRequest.into(imageLoadConfig.imageView);
}
@Override
public void clearCache(Context context) {
if (context == null) throw new NullPointerException("context is required");
final Glide glide = GlideApp.get(context);
//清除内存缓存
glide.clearMemory();
//清除硬盘缓存
new Thread(new Runnable() {
@Override
public void run() {
glide.clearDiskCache();
}
}).start();
}
}
同理,后期你需要替换成picasso框架的话,只需要再写个picasso的实现类即可。
最后,我们再写个单例吧,给一个初始化的方法,这样我们可以在application中去初始化我们要使用哪种图片加载框架
public class ImageLoadUtil {
private static volatile ImageLoadUtil instance;
private BaseImageLoadInterface imageLoadInterface;
private ImageLoadUtil(){
}
public static ImageLoadUtil getInstance(){
if (instance == null) {
synchronized (ImageLoadUtil.class){
if (instance == null) {
instance = new ImageLoadUtil();
}
}
}
return instance;
}
public void init(BaseImageLoadInterface imageLoadInterface){
this.imageLoadInterface = imageLoadInterface;
}
public <T extends BaseImageLoadConfig> void loadImage(Context context, T imageLoadConfig){
if (imageLoadInterface == null) {
throw new IllegalStateException("You must call init() to initialize first");
}
imageLoadInterface.loadImage(context, imageLoadConfig);
}
public void clearCache(Context context){
if (imageLoadInterface == null) {
throw new IllegalStateException("You must call init() to initialize first");
}
imageLoadInterface.clearCache(context);
}
}
最终在activity中调用效果正如文章开头所示,这里再贴一下
ImageLoadUtil.getInstance().loadImage(this,
GlideImageLoadConfig.builder()
.load(url)
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(imageView)
.build());
//清理缓存
clearBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ImageLoadUtil.getInstance().clearCache(GlideActivity.this);
}
});
这样使用起来是不是很清爽,即使以后要换框架也不需要修改调用处的代码了,直接再写个特定框架的配置类和具体的策略实现类即可。代码中列举的是一些常用的属性,有些没有给出,大家可以根据自己的需求自行增删。