通过前面几篇博文。我们分析了 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,详细的解释大家自己去查吧。我就不在这里多说了。
那么作者在这里实际上就是在接口内部定义了一个单例。
What is an efficient way to implement a singleton pattern in Java?
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;
BlockingQueue<Runnable> taskQueue =
lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
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 调用的简化。大家自己看看源代码都能看懂的~