对项目中图片加载框架进一步解耦封装

大家在做项目中肯定都会用到一些第三方图片加载库,比如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);
            }
        });

这样使用起来是不是很清爽,即使以后要换框架也不需要修改调用处的代码了,直接再写个特定框架的配置类和具体的策略实现类即可。代码中列举的是一些常用的属性,有些没有给出,大家可以根据自己的需求自行增删。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,709评论 2 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,974评论 25 707
  • 【Android 库 Glide】 引用 Android图片加载框架最全解析(一),Glide的基本用法Andro...
    Rtia阅读 5,419评论 0 22
  • 今天是6月21日,2018年的6月份已见底,想想自己制定的新年计划完成了多少,有哪些程序依然在进行中。总结其原因是...
    赵墨香阅读 246评论 0 2
  • 李阳牵着翔儿的小手,缓缓往小区门口走去。三个月以来他内心的挣扎与苦痛在见到林玲的那一刻起,却越发浓烈。原来,他始终...
    和春天有约阅读 1,415评论 40 31