通过前面几篇博文。我们分析了 AUI 的缓存、工具类、显示与载入这几个方面的代码。今天呢,我们继续研究 AUI 的源代码,学习当中的核心辅助工具类。
希望大家能在里面学到东西哈。
Download
要下载一张图片,我们想象须要什么哈:首先我们得设定支持的协议。得有下载的链接。由对应的下载链接去下载图片。进而得到图片的输入流,剩下的就交给图片的显示/载入类处理啦。
那么我们能够设计出以下的接口:
public interface ImageDownloader { InputStream getStream(String imageUri, Object extra) throws IOException; public enum Scheme { HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN(""); private String scheme; private String uriPrefix; Scheme(String scheme) { this.scheme = scheme; uriPrefix = scheme + "://"; } public static Scheme ofUri(String uri) { if (uri != null) { for (Scheme s : values()) { if (s.belongsTo(uri)) { return s; } } } return UNKNOWN; } private boolean belongsTo(String uri) { return uri.toLowerCase(Locale.US).startsWith(uriPrefix); } public String wrap(String path) { return uriPrefix + path; } public String crop(String uri) { if (!belongsTo(uri)) { throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme)); } return uri.substring(uriPrefix.length()); } }}
看到这里大家可能会疑惑了,我们不是设计接口么,为啥要搞个枚举类型,并且还在里面搞那么多乱七八糟的东西……事实上。假设大家有看过《Thinking In Java》的话就会知道。在 Java 中,enum 实际上就是一个类。由于 enum 定义后的枚举类在编译时默认继承 java.lang.Enum 类。而该枚举类会自己主动被加上 final 关键字修饰。这也使得枚举类无法被继承。更详细的解释大家能够自行 Google 哈。
那么为什么要在 ImageDownloader 里引入这个枚举类呢?我们最好还是先看看枚举类内究竟有什么。在枚举类 Scheme 中,主要有四个方法,而这四个方法都用于处理 Uri,如:裁减 Uri、加入 Uri 前缀、推断 Uri。也就是说,Scheme 的抽象职责是:对 Uri 进行修饰。
而 Scheme 对 Uri 的修饰结果将交给 ImageDownloader 完毕下载操作。
换言之,Scheme 的抽象与 ImageDownloader 的抽象实际上是不一致的(ImageDownloader 的抽象是下载图片,而下载图片所需的 Uri 须要进行什么处理才干被 ImageDownloader 使用,并完毕下载事实上不重要),为了让减少类的耦合度,我们在 ImageDownloader 的内部实现了 Scheme 类。
那么有人可能会问了,那我们另外创建一个类不行么?就我的理解来看,肯定是能够的。由于 Scheme 本质上也是一个类,我们新创建一个类。声明为 final 类。加入对应的静态常量、方法。事实上效果也是一样的。
那作者为什么要这么干呢?看过《Effective Java》的朋友可能会知道,实现单例的最佳方法就是使用 enum,详细的解释大家自己去查吧。我就不在这里多说了。
那么作者在这里实际上就是在接口内部定义了一个单例。
BaseImageDownloader 实现了 ImageDownloader 接口,并且依据我们的需求实现了对应的图片下载细节,详细没什么好解说的,大家能够自行阅读源代码哈~
Listener
在 AUI 中,实际上须要用到的 Listener 并不多,毕竟图片载入仅仅是一个非常小的功能模块嘛。那么 AUI 究竟包括了什么 Listener 呢?
- 图片载入监听器:监听图片载入的開始、结束、失败、取消
- 图片载入进度监听器:监听图片载入的进度
- 图片载入滚动监听器:当图片载入正在进行,若发生滚动,则停止载入,换言之,仅仅载入当前屏幕显示的图片。图片滚动过程的图片则不载入。
可能有人会认为非常奇怪。为什么图片载入监听器和图片载入进度监听器要分开实现。事实上我也想不懂。希望有人能给我个解释……
Assist
在 assist 里面有几个类我们在之前的博文中已经有提过了,我就不再这反复拉。
比較简单的类我也会一笔带过。希望大家理解哈。
deque
在这里面都是一些双端队列。比如 LinkedBlockingDeque、LIFOLinkedBlockingDeque。双端队列的对应知识。以及详细实现我相信不用我在这里废话了,毕竟数据结构的课程中一定会说到这个知识点,这也是个主要的、必须掌握的数据结构。
那么在这里我们须要了解什么呢?那就是:为什么引入双端队列作为 AUI 的数据结构,双端队列较之其它数据结构在这个应用场景下有什么长处。
我们最好还是先看看 Deque 在哪里被用到吧,在 AUI 库中搜索发现。DefaultConfigurationFactory 调用了 Deque。
那么 DefaultConfigurationFactory 究竟是什么呢?
从该类的命名以及内部的方法名我们能够知道。DefaultConfigurationFactory 就是 AUI 默认的配置工厂类,假设开发人员没有自己定义对应的配置选项的话,AUI 就会使用这个类所设置的默认选项,完毕对应的载入、下载、缓存等等……
最好还是看看以下的代码段:
public static Executor createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType) { boolean lifo = tasksProcessingType == QueueProcessingType.LIFO; BlockingQueuetaskQueue = lifo ? new LIFOLinkedBlockingDeque () : new LinkedBlockingQueue (); return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue, createThreadFactory(threadPriority, "uil-pool-")); }
在这段代码中。我们会获得线程池,并且用 LIFOLinkedBlockingDeque 作为线程的处理队列。也就是说,在 AUI 库中。双端队列这个数据结构是用来完毕 AUI 线程处理的。那么为什么要选择双端队列,为什么又选择 LIFOLinkedBlockingDeque 作为默认选项呢?
之所以选择双端队列。是由于 ThreadPoolExecutor 使用的是生产者-消费者模式,在 ThreadPoolExecutor 类内部使用 BlockingQueue 作为任务处理队列能满足生产者-消费者模式对任务存储数据结构的要求。而在我们的 assist 中。LIFOLinkedBlockingDeque 是 LinkedBlockingDeque 的子类。而 LinkedBlockingDeque 实现了 BlockingDeque 接口,BlockingDeque 接口又继承于 BlockingQueue。
Others
在 assist 中剩下的辅助类我认为都挺简单的,都是一些基本 API 调用的简化。大家自己看看源代码都能看懂的~