设计自己的ImageLoader图片加载框架

 一、框架实现的功能:

1.实现了基于LRUCache的内存缓存机制;

2.实现了基于DiskLRUCache的sd卡缓存机制;

3.实现了基于httpconnection的网络下载图片机制;

4.实现了根据指定宽高压缩图片;

5.实现了未指定宽高时根据imageview控件宽高压缩图片;

6.基于面向对象6大原则使得程序本身更加稳定、更加灵活、更好扩展。


二、框架执行流程分析:

设计自己的ImageLoader图片加载框架_第1张图片


三、框架基本模块拆分:

1.imageloader模块,负责获取图片并显示图片;

2.内存缓存模块;

3.本地缓存模块;

4.网络下载模块;


四、框架设计思想:

1.根据单一职责原则,我们的每个类都应该只负责一种职责,这样会使每个类中的代码量减少,并且职责清晰,当需要更改某一种职责的逻辑时,不用牵涉其他类。举例:我们的内存缓存模块,就只负责内存缓存的逻辑,而我们的本地缓存模块,就只负责本地缓存逻辑,这样当我们需要修改本地缓存逻辑时,例如需要修改本地缓存使用的方法从直接保存到sd卡,改成使用diskLruCache,这时候就只需要修改本地缓存模块就好了,不需要去动内存缓存的相关逻辑,如果出错,也只会是本地缓存模块出错,而不会影响到其他模块;


2.根据开闭原则,我们的模块应该对于扩展是开发的,但是对于修改是封闭的,在框架的升级更新过程中,如果对源代码进行修改可能会引入新的错误,破坏原始框架,我们希望尽量通过扩展的方式来实现变化,当然也只是尽量遵循这个原则,修改原始代码也是不可避免的。举例:我们的框架实现了从路径加载图片的功能,然后需要增加从res文件夹中加载图片的功能,这时候我们应该在继承imageloader这个类的基础上去增加新的功能,而不是直接修改imageloader这个类,一是容易把imageloader类改错了,二是有些人可能根本不需要这个功能,这只是你自己需要的一种自定义的功能;


3.建立抽象,通过抽象建立规范,在运行时用具体的实现替代抽象,来实现可扩展性,使得项目更加灵活多变。举例:如果我们一开始只实现了内存缓存功能,这时候我们发现还需要本地缓存功能,此时我们应该做的不应该是在内存缓存中直接加入本地缓存功能,原因看第2条。你说那继承内存缓存类再增加本地缓存功能?也不好,原因是我们的本地缓存本来就和内存缓存没什么关系,而且需要本地缓存的用户也不一定就还需要内存缓存功能,这样就造成了本地缓存模块的代码冗余。我们应该抽象出一个缓存接口,让每一个缓存模块都实现这个接口,这样就既实现了程序的可扩展性,又不会让代码冗余,也解释了第4条,为什么抽象不应该依赖细节;


4.根据依赖倒置原则,高层模块不应该依赖底层模块,二者都应该依赖抽象。抽象不应该依赖细节。细节应该依赖抽象。


以上这四点是我们面向对象设计时的几个基本原则,还有一些,我的语言表达能力有限感觉表述的可能也不太清楚,如果感兴趣可以自己去看看《Android源码设计模式解析与实战》这本书的第一章就是了。

所以我们的imageloader框架设计完的UML图是这样的:

设计自己的ImageLoader图片加载框架_第2张图片

接下来就是代码实现了,我复制一部分主要的过来大家看看就好:

1.首先来看缓存的抽象接口ImageCache:

/**
 *定义缓存接口,让所有图片缓存类型都实现这个接口,实现开闭原则
 * Created by zd on 2017/12/12.
 */

public interface ImageCache<T> {

    /**
     * 将对象压入缓存的方法
     * @param key 对象在缓存中对应的唯一key值
     * @param object 对象本身,内存缓存时用的是bitmap类型,本地缓存时用的是流对象,所以这个地方用的是一个泛型对象
     *               方便以后自定义缓存类型时使用
     * @param requireWidth 期望宽度
     * @param requireHeight 期望高度
     */
    void put(String key, T object, int requireWidth, int requireHeight);

    /**
     * 从缓存中取出图片的方法
     * @param key 图片在缓存中对应的唯一key值
     * @param requireWidth 图片的期望宽度
     * @param requireHeight 图片的期望高度
     * @return
     */
    Bitmap get(String key, int requireWidth, int requireHeight);

    /**
     * 图片是否使用了本地缓存,网络下载的时候判断是将流直接转换成图片还是缓存到本地
     * @return 使用了本地缓存返回true,否则返回false
     */
    boolean isUseDiskCache();
}
缓存的具体实现就不看了,内存缓存就是用的LruCache,本地缓存用的DiskLruCache都很简单。


2.再来看看网络下载接口:

public interface ImageDownLoad {

    /**
     * 从网络下载图片的方法
     * @param cache 使用的缓存对象,因为下载完成后还要把图片压入缓存,方便下次直接从缓存读取
     * @param url 下载路径
     * @param key 图片对应的唯一key值
     * @param requireWidth 图片期望宽度
     * @param requireHeight 图片期望高度
     * @return 封装的一个包含图片、url、key等信息的实体类
     */
    ImageBean getFromHttp(ImageCache cache, String url, String key, int requireWidth, int requireHeight);
}
网络下载图片的具体方法也不看了,我是用的httpURLConnection请求网络,然后获取流对象,将流转换成bitmap对象或者写入到sd卡中,需要注意的是流对象直接生成bitmap对象时,因为要先读一次图片边界,导致流的指针到了最后,再次读取流生成的bitmap就会为空,需要用流的mark和reset方法使得读过的流还可以再读一次才能获取正确的bitmap对象。如果需要可以到文章结束点击GitHub上的源码链接中去看。


3.最后看一下ImagelLoader类的实现,我采用了线程池的方式来控制子线程实现耗时操作。

/**
 * 为了满足单一职责原则,这个类只负责图片的加载功能
 * Created by zd on 2017/12/4.
 */

public class ImageLoader {
//图片缓存
private ImageCache cache;
//图片下载
private ImageDownLoad downLoad;
/**执行本地操作和网络操作的线程池*/
private ExecutorService executorService;
//主线程更新imageview显示图片的handler
private Handler handler;

/**
 * 将图片加载到imageview上
 * @param url 图片加载路径
 * @param iv imageview控件
 * @param requireWidth 期望图片宽度,如果你希望使用图片的原始宽高,
 *                     不进行缩放处理,这里可以使用{@link ImageSizeUtil#OriginalSize}
 * @param requireHeight 期望图片高度,如果你希望使用图片的原始宽高,
 *                     不进行缩放处理,这里可以使用{@link ImageSizeUtil#OriginalSize}
 */
public void BindView(String url, ImageView iv, int requireWidth, int requireHeight) {

    String key = HashKey.hashKeyFromUrl(url);
    iv.setTag(key);

    SoftReference imageView = new SoftReference(iv);
    executorService.execute(buildTask(url, key, imageView, requireWidth, requireHeight));
}

/**
 * 指定图片缓存类型
 * @param cache 图片缓存类型,已经实现的图片缓存类型有{@link MemoryCache}内存缓存,
 *              {@link DiskLruCache}本地缓存,
 *              {@link com.zd.imageloaderlibrary.cache.DoubleCache}内存与本地双缓存,
 *              你也可以根据自己的需要自定义实现缓存,如果不指定缓存类型,默认的是只内存缓存
 */
public ImageLoader setImageCache(ImageCache cache) {

    this.cache = cache;
    return imageLoader;
}

/**
 * 指定使用哪种网络请求方式下载图片
 * @param downLoad 下载图片的网络请求方式,imageloader默认的是{@link HttpDownLoad},
 *                 如果您不喜欢这个种网络请求方式也可以自定义。
 *
 */
public ImageLoader setImageDownLoad(ImageDownLoad downLoad) {

    this.downLoad = downLoad;
    return imageLoader;
}


private Runnable buildTask(final String url, final String key,
                           final SoftReference iv,
                           final int requireWidth, final int requireHeight) {

    return new Runnable() {
        @Override
        public void run() {

            Bitmap bitmap = null;

            bitmap = cache.get(key, requireWidth, requireHeight);
            if (bitmap != null) {

                sendSuccessMessageToHandler(key, iv, bitmap);

            } else {

                ImageBean bean = downLoad.
                        getFromHttp(cache, url, key, requireWidth, requireHeight);
                bitmap = bean.getBm();
                if (bitmap != null) {

                    sendSuccessMessageToHandler(key, iv, bitmap);
                } else {

                    sendErrorMessageToHandler(bean.getMsg());
                }
            }
        }
    };
}

}

通过上面的代码可以看到ImageLoader中的缓存对象是ImageCache这个抽象接口类型,默认使用的是内存缓存,你也可以通过setImageCache方法设置你喜欢的缓存方式,网络下载同理,而加载图片时,我们只是调用了ImageCache的get方法判断图片是否存在,也不用再次关心缓存的具体实现内存了,代码量很少,可阅读性增强很多,不会深入代码细节无法自拔,网络下载同理。


4.使用ImageLoader的方法:

1).只是用内存缓存时:

ImageLoader.getNewInstance().BindView(url, ImageView);

或者ImageLoader.getNewInstance().BindView(url, ImageView, requireWidth, requireHeight);

2).使用内存和本地缓存双缓存时:

ImageLoader.getNewInstance().setImageCache(DoubleCache.getInstance(path)).BindView(url, ImageView);

或者

ImageLoader.getNewInstance().setImageCache(DoubleCache.getInstance(path))

.BindView(url, ImageView, requireWidth, requireHeight);

使用本地缓存和自定义缓存时和使用双缓存时的方法类似就不写了。

最后大家如果想看源码可以到GitHub上看看,连接奉上,感谢阅读,最后这个框架还有很多不完善的功能,如果有朋友感兴趣,也可以作为开源项目,大家一起来添砖加瓦,看看最后能做成什么样子。


你可能感兴趣的:(工具类)