一、框架实现的功能:
1.实现了基于LRUCache的内存缓存机制;
2.实现了基于DiskLRUCache的sd卡缓存机制;
3.实现了基于httpconnection的网络下载图片机制;
4.实现了根据指定宽高压缩图片;
5.实现了未指定宽高时根据imageview控件宽高压缩图片;
6.基于面向对象6大原则使得程序本身更加稳定、更加灵活、更好扩展。
二、框架执行流程分析:
三、框架基本模块拆分:
1.imageloader模块,负责获取图片并显示图片;
2.内存缓存模块;
3.本地缓存模块;
4.网络下载模块;
四、框架设计思想:
1.根据单一职责原则,我们的每个类都应该只负责一种职责,这样会使每个类中的代码量减少,并且职责清晰,当需要更改某一种职责的逻辑时,不用牵涉其他类。举例:我们的内存缓存模块,就只负责内存缓存的逻辑,而我们的本地缓存模块,就只负责本地缓存逻辑,这样当我们需要修改本地缓存逻辑时,例如需要修改本地缓存使用的方法从直接保存到sd卡,改成使用diskLruCache,这时候就只需要修改本地缓存模块就好了,不需要去动内存缓存的相关逻辑,如果出错,也只会是本地缓存模块出错,而不会影响到其他模块;
2.根据开闭原则,我们的模块应该对于扩展是开发的,但是对于修改是封闭的,在框架的升级更新过程中,如果对源代码进行修改可能会引入新的错误,破坏原始框架,我们希望尽量通过扩展的方式来实现变化,当然也只是尽量遵循这个原则,修改原始代码也是不可避免的。举例:我们的框架实现了从路径加载图片的功能,然后需要增加从res文件夹中加载图片的功能,这时候我们应该在继承imageloader这个类的基础上去增加新的功能,而不是直接修改imageloader这个类,一是容易把imageloader类改错了,二是有些人可能根本不需要这个功能,这只是你自己需要的一种自定义的功能;
3.建立抽象,通过抽象建立规范,在运行时用具体的实现替代抽象,来实现可扩展性,使得项目更加灵活多变。举例:如果我们一开始只实现了内存缓存功能,这时候我们发现还需要本地缓存功能,此时我们应该做的不应该是在内存缓存中直接加入本地缓存功能,原因看第2条。你说那继承内存缓存类再增加本地缓存功能?也不好,原因是我们的本地缓存本来就和内存缓存没什么关系,而且需要本地缓存的用户也不一定就还需要内存缓存功能,这样就造成了本地缓存模块的代码冗余。我们应该抽象出一个缓存接口,让每一个缓存模块都实现这个接口,这样就既实现了程序的可扩展性,又不会让代码冗余,也解释了第4条,为什么抽象不应该依赖细节;
4.根据依赖倒置原则,高层模块不应该依赖底层模块,二者都应该依赖抽象。抽象不应该依赖细节。细节应该依赖抽象。
以上这四点是我们面向对象设计时的几个基本原则,还有一些,我的语言表达能力有限感觉表述的可能也不太清楚,如果感兴趣可以自己去看看《Android源码设计模式解析与实战》这本书的第一章就是了。
所以我们的imageloader框架设计完的UML图是这样的:
接下来就是代码实现了,我复制一部分主要的过来大家看看就好:
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); SoftReferenceimageView = 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 SoftReferenceiv, 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()); } } } }; }
}
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上看看,连接奉上,感谢阅读,最后这个框架还有很多不完善的功能,如果有朋友感兴趣,也可以作为开源项目,大家一起来添砖加瓦,看看最后能做成什么样子。