天天看點

從設計到實作,一步步教你實作Android-Universal-ImageLoader-輔助類

通過前面幾篇博文,我們分析了 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, L, TimeUnit.MILLISECONDS, taskQueue,
                createThreadFactory(threadPriority, "uil-pool-"));
    }
           

在這段代碼中,我們會獲得線程池,并且用 LIFOLinkedBlockingDeque 作為線程的處理隊列。也就是說,在 AUI 庫中,雙端隊列這個資料結構是用來完成 AUI 線程處理的。那麼為什麼要選擇雙端隊列,為什麼又選擇 LIFOLinkedBlockingDeque 作為預設選項呢?

之是以選擇雙端隊列,是因為 ThreadPoolExecutor 使用的是生産者-消費者模式,在 ThreadPoolExecutor 類内部使用 BlockingQueue 作為任務處理隊列能滿足生産者-消費者模式對任務存儲資料結構的要求。而在我們的 assist 中,LIFOLinkedBlockingDeque 是 LinkedBlockingDeque 的子類,而 LinkedBlockingDeque 實作了 BlockingDeque 接口,BlockingDeque 接口又繼承于 BlockingQueue。

Others

在 assist 中剩下的輔助類我覺得都挺簡單的,都是一些基本 API 調用的簡化,大家自己看看源碼都能看懂的~