Android 開源專案原始碼解析 -->Android Universal Image Loader 原始碼分析(十四)

Wei_Leng發表於2016-09-27

專案:Android-Universal-Image-Loader

1. 功能介紹

1.1 Android Universal Image Loader

Android Universal Image Loader 是一個強大的、可高度定製的圖片快取,本文簡稱為UIL
簡單的說 UIL 就做了一件事——獲取圖片並顯示在相應的控制元件上。

1.2 基本使用

1.2.1 初始化

新增完依賴後在ApplicationActivity中初始化ImageLoader,如下:

public class YourApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
            // 新增你的配置需求
            .build();
        ImageLoader.getInstance().init(configuration);
    }
}

其中 configuration 表示ImageLoader的配置資訊,可包括圖片最大尺寸、執行緒池、快取、下載器、解碼器等等。

1.2.2 Manifest 配置
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:name=".YourApplication"
        …… >
        ……
    </application>
</manifest>

新增網路許可權。如果允許磁碟快取,需要新增寫外設的許可權。

1.2.3 下載顯示圖片

下載圖片,解析為 Bitmap 並在 ImageView 中顯示。

imageLoader.displayImage(imageUri, imageView);

下載圖片,解析為 Bitmap 傳遞給回撥介面。

imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        // 圖片處理
    }
});

以上是簡單使用,更復雜 API 見本文詳細設計

1.3 特點

  • 可配置度高。支援任務執行緒池、下載器、解碼器、記憶體及磁碟快取、顯示選項等等的配置。
  • 包含記憶體快取和磁碟快取兩級快取。
  • 支援多執行緒,支援非同步和同步載入。
  • 支援多種快取演算法、下載進度監聽、ListView 圖片錯亂解決等。

2. 總體設計

2.1. 總體設計圖

總體設計圖

上面是 UIL 的總體設計圖。整個庫分為ImageLoaderEngineCacheImageDownloaderImageDecoderBitmapDisplayerBitmapProcessor五大模組,其中Cache分為MemoryCacheDiskCache兩部分。

簡單的講就是ImageLoader收到載入及顯示圖片的任務,並將它交給ImageLoaderEngineImageLoaderEngine分發任務到具體執行緒池去執行,任務通過CacheImageDownloader獲取圖片,中間可能經過BitmapProcessorImageDecoder處理,最終轉換為Bitmap交給BitmapDisplayerImageAware中顯示。

2.2. UIL 中的概念

簡單介紹一些概念,在4. 詳細設計中會仔細介紹。
ImageLoaderEngine:任務分發器,負責分發LoadAndDisplayImageTaskProcessAndDisplayImageTask給具體的執行緒池去執行,本文中也稱其為engine,具體參考4.2.6 ImageLoaderEngine.java

ImageAware:顯示圖片的物件,可以是ImageView等,具體參考4.2.9 ImageAware.java

ImageDownloader:圖片下載器,負責從圖片的各個來源獲取輸入流, 具體參考4.2.22 ImageDownloader.java

Cache:圖片快取,分為MemoryCacheDiskCache兩部分。

MemoryCache:記憶體圖片快取,可向記憶體快取快取圖片或從記憶體快取讀取圖片,具體參考4.2.24 MemoryCache.java

DiskCache:本地圖片快取,可向本地磁碟快取儲存圖片或從本地磁碟讀取圖片,具體參考4.2.38 DiskCache.java

ImageDecoder:圖片解碼器,負責將圖片輸入流InputStream轉換為Bitmap物件, 具體參考4.2.53 ImageDecoder.java

BitmapProcessor:圖片處理器,負責從快取讀取或寫入前對圖片進行處理。具體參考4.2.61 BitmapProcessor.java

BitmapDisplayer:Bitmap物件顯示在相應的控制元件ImageAware上, 具體參考4.2.56 BitmapDisplayer.java

LoadAndDisplayImageTask:用於載入並顯示圖片的任務, 具體參考4.2.20 LoadAndDisplayImageTask.java

ProcessAndDisplayImageTask:用於處理並顯示圖片的任務, 具體參考4.2.19 ProcessAndDisplayImageTask.java

DisplayBitmapTask:用於顯示圖片的任務, 具體參考4.2.18 DisplayBitmapTask.java

3. 流程圖

上圖為圖片載入及顯示流程圖,在 uil 庫中給出,這裡用中文重新畫出。

4. 詳細設計

4.1 類關係圖

4.2 核心類功能介紹

4.2.1 ImageLoader.java

圖片載入器,對外的主要 API,採取了單例模式,用於圖片的載入和顯示。

主要函式:

(1). getInstance()

得到ImageLoader的單例。通過雙層是否為 null 判斷提高效能。

(2). init(ImageLoaderConfiguration configuration)

初始化配置引數,引數configurationImageLoader的配置資訊,包括圖片最大尺寸、任務執行緒池、磁碟快取、下載器、解碼器等等。
實現中會初始化ImageLoaderEngine engine屬性,該屬性為任務分發器。

(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

載入並顯示圖片或載入並執行回撥介面。ImageLoader 載入圖片主要分為三類介面:

  • displayImage(…) 表示非同步載入並顯示圖片到對應的ImageAware上。
  • loadImage(…) 表示非同步載入圖片並執行回撥介面。
  • loadImageSync(…) 表示同步載入圖片。

以上三類介面最終都會呼叫到這個函式進行圖片載入。函式引數解釋如下:
uri: 圖片的 uri。uri 支援多種來源的圖片,包括 http、https、file、content、assets、drawable 及自定義,具體介紹可見ImageDownloader
imageAware: 一個介面,表示需要載入圖片的物件,可包裝 View。
options: 圖片顯示的配置項。比如載入前、載入中、載入失敗應該顯示的佔點陣圖片,圖片是否需要在磁碟快取,是否需要在記憶體快取等。
listener: 圖片載入各種時刻的回撥介面,包括開始載入、載入失敗、載入成功、取消載入四個時刻的回撥函式。
progressListener: 圖片載入進度的回撥介面。

函式流程圖如下:


ImageLoader Display Image Flow Chart

4.2.2 ImageLoaderConfiguration.java

ImageLoader的配置資訊,包括圖片最大尺寸、執行緒池、快取、下載器、解碼器等等。

主要屬性:

(1). Resources resources

程式本地資源訪問器,用於載入DisplayImageOptions中設定的一些 App 中圖片資源。

(2). int maxImageWidthForMemoryCache

記憶體快取的圖片最大寬度。

(3). int maxImageHeightForMemoryCache

記憶體快取的圖片最大高度。

(4). int maxImageWidthForDiskCache

磁碟快取的圖片最大寬度。

(5). int maxImageHeightForDiskCache

磁碟快取的圖片最大高度。

(6). BitmapProcessor processorForDiskCache

圖片處理器,用於處理從磁碟快取中讀取到的圖片。

(7). Executor taskExecutor

ImageLoaderEngine中用於執行從源獲取圖片任務的 Executor。

(18). Executor taskExecutorForCachedImages

ImageLoaderEngine中用於執行從快取獲取圖片任務的 Executor。

(19). boolean customExecutor

使用者是否自定義了上面的 taskExecutor。

(20). boolean customExecutorForCachedImages

使用者是否自定義了上面的 taskExecutorForCachedImages。

(21). int threadPoolSize

上面兩個預設執行緒池的核心池大小,即最大併發數。

(22). int threadPriority

上面兩個預設執行緒池的執行緒優先順序。

(23). QueueProcessingType tasksProcessingType

上面兩個預設執行緒池的執行緒佇列型別。目前只有 FIFO, LIFO 兩種可供選擇。

(24). MemoryCache memoryCache

圖片記憶體快取。

(25). DiskCache diskCache

圖片磁碟快取,一般放在 SD 卡。

(26). ImageDownloader downloader

圖片下載器。

(27). ImageDecoder decoder

圖片解碼器,內部可使用我們常用的BitmapFactory.decode(…)將圖片資源解碼成Bitmap物件。

(28). DisplayImageOptions defaultDisplayImageOptions

圖片顯示的配置項。比如載入前、載入中、載入失敗應該顯示的佔點陣圖片,圖片是否需要在磁碟快取,是否需要在記憶體快取等。

(29). ImageDownloader networkDeniedDownloader

不允許訪問網路的圖片下載器。

(30). ImageDownloader slowNetworkDownloader

慢網路情況下的圖片下載器。

4.2.3 ImageLoaderConfiguration.Builder.java 靜態內部類

Builder 模式,用於構造引數繁多的ImageLoaderConfiguration
其屬性與ImageLoaderConfiguration類似,函式多是屬性設定函式。

主要函式及含義:

(1). build()

按照配置,生成 ImageLoaderConfiguration。程式碼如下:

public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}
(2). initEmptyFieldsWithDefaultValues()

初始化值為null的屬性。若使用者沒有配置相關項,UIL 會通過呼叫DefaultConfigurationFactory中的函式返回一個預設值當配置。
taskExecutorForCachedImagestaskExecutorImageLoaderEnginetaskDistributor的預設值如下:

parameters taskDistributor taskExecutorForCachedImages/taskExecutor
corePoolSize 0 3
maximumPoolSize Integer.MAX_VALUE 3
keepAliveTime 60 0
unit SECONDS MILLISECONDS
workQueue SynchronousQueue LIFOLinkedBlockingDeque / LinkedBlockingQueue
priority 5 3

diskCacheFileNameGenerator預設值為HashCodeFileNameGenerator
memoryCache預設值為LruMemoryCache。如果記憶體快取不允許快取一張圖片的多個尺寸,則用FuzzyKeyMemoryCache做封裝,同一個圖片新的尺寸會覆蓋快取中該圖片老的尺寸。
diskCache預設值與diskCacheSizediskCacheFileCount值有關,如果他們有一個大於 0,則預設為LruDiskCache,否則使用無大小限制的UnlimitedDiskCache
downloader預設值為BaseImageDownloader
decoder預設值為BaseImageDecoder
詳細及其他屬性預設值請到DefaultConfigurationFactory中檢視。

(3). denyCacheImageMultipleSizesInMemory()

設定記憶體快取不允許快取一張圖片的多個尺寸,預設允許。
後面會講到 View 的 getWidth() 在初始化前後的不同值與這個設定的關係。

(4). diskCacheSize(int maxCacheSize)

設定磁碟快取的最大位元組數,如果大於 0 或者下面的maxFileCount大於 0,預設的DiskCache會用LruDiskCache,否則使用無大小限制的UnlimitedDiskCache

(5). diskCacheFileCount(int maxFileCount)

設定磁碟快取資料夾下最大檔案數,如果大於 0 或者上面的maxCacheSize大於 0,預設的DiskCache會用LruDiskCache,否則使用無大小限制的UnlimitedDiskCache

4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 靜態內部類

不允許訪問網路的圖片下載器,實現了ImageDownloader介面。
實現也比較簡單,包裝一個ImageDownloader物件,通過在 getStream(…) 函式中禁止 Http 和 Https Scheme 禁止網路訪問,如下:

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            throw new IllegalStateException();
        default:
            return wrappedDownloader.getStream(imageUri, extra);
    }
}
4.2.5 ImageLoaderConfiguration.SlowNetworkImageDownloader.java 靜態內部類

慢網路情況下的圖片下載器,實現了ImageDownloader介面。
通過包裝一個ImageDownloader物件實現,在 getStream(…) 函式中當 Scheme 為 Http 和 Https 時,用FlushedInputStream代替InputStream處理慢網路情況,具體見後面FlushedInputStream的介紹。

4.2.6 ImageLoaderEngine.java

LoadAndDisplayImageTaskProcessAndDisplayImageTask任務分發器,負責分發任務給具體的執行緒池。

主要屬性:

(1). ImageLoaderConfiguration configuration

ImageLoader的配置資訊,可包括圖片最大尺寸、執行緒池、快取、下載器、解碼器等等。

(2). Executor taskExecutor

用於執行從源獲取圖片任務的 Executor,為configuration中的 taskExecutor,如果為null,則會呼叫DefaultConfigurationFactory.createExecutor(…)根據配置返回一個預設的執行緒池。

(3). Executor taskExecutorForCachedImages

用於執行從快取獲取圖片任務的 Executor,為configuration中的 taskExecutorForCachedImages,如果為null,則會呼叫DefaultConfigurationFactory.createExecutor(…)根據配置返回一個預設的執行緒池。

(4). Executor taskDistributor

任務分發執行緒池,任務指LoadAndDisplayImageTaskProcessAndDisplayImageTask,因為只需要分發給上面的兩個 Executor 去執行任務,不存在較耗時或阻塞操作,所以用無併發數(Int 最大值)限制的執行緒池即可。

(5). Map cacheKeysForImageAwares

ImageAware與記憶體快取 key 對應的 map,key 為ImageAware的 id,value 為記憶體快取的 key。

(6). Map uriLocks

圖片正在載入的重入鎖 map,key 為圖片的 uri,value 為標識其正在載入的重入鎖。

(7). AtomicBoolean paused

是否被暫停。如果為true,則所有新的載入或顯示任務都會等待直到取消暫停(為false)。

(8). AtomicBoolean networkDenied

是否不允許訪問網路,如果為true,通過ImageLoadingListener.onLoadingFailed(…)獲取圖片,則所有不在快取中需要網路訪問的請求都會失敗,返回失敗原因為網路訪問被禁止

(9). AtomicBoolean slowNetwork

是否是慢網路情況,如果為true,則自動呼叫SlowNetworkImageDownloader下載圖片。

(10). Object pauseLock

暫停的等待鎖,可在engine被暫停後呼叫這個鎖等待。

主要函式:

(1). void submit(final LoadAndDisplayImageTask task)

新增一個LoadAndDisplayImageTask。直接用taskDistributor執行一個 Runnable,在 Runnable 內部根據圖片是否被磁碟快取過確定使用taskExecutorForCachedImages還是taskExecutor執行該 task。

(2). void submit(ProcessAndDisplayImageTask task)

新增一個ProcessAndDisplayImageTask。直接用taskExecutorForCachedImages執行該 task。

(3). void pause()

暫停圖片載入任務。所有新的載入或顯示任務都會等待直到取消暫停(為false)。

(4). void resume()

繼續圖片載入任務。

(5). stop()

暫停所有載入和顯示圖片任務並清除這裡的內部屬性值。

(6). fireCallback(Runnable r)

taskDistributor立即執行某個任務。

(7). getLockForUri(String uri)

得到某個 uri 的重入鎖,如果不存在則新建。

(8). createTaskExecutor()

呼叫DefaultConfigurationFactory.createExecutor(…)建立一個執行緒池。

(9). getLoadingUriForView(ImageAware imageAware)

得到某個imageAware正在載入的圖片 uri。

(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)

準備開始一個Task。向cacheKeysForImageAwares中插入ImageAware的 id 和圖片在記憶體快取中的 key。

(11). void cancelDisplayTaskFor(ImageAware imageAware)

取消一個顯示任務。從cacheKeysForImageAwares中刪除ImageAware對應元素。

(12). denyNetworkDownloads(boolean denyNetworkDownloads)

設定是否不允許網路訪問。

(13). handleSlowNetwork(boolean handleSlowNetwork)

設定是否慢網路情況。

4.2.7 DefaultConfigurationFactory.java

ImageLoaderConfigurationImageLoaderEngine提供一些預設配置。

主要函式:

(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)

建立執行緒池。
threadPoolSize表示核心池大小(最大併發數)。
threadPriority表示執行緒優先順序。
tasksProcessingType表示執行緒佇列型別,目前只有 FIFO, LIFO 兩種可供選擇。
內部實現會呼叫createThreadFactory(…)返回一個支援執行緒優先順序設定,並且以固定規則命名新建的執行緒的執行緒工廠類DefaultConfigurationFactory.DefaultThreadFactory

(2). createTaskDistributor()

ImageLoaderEngine中的任務分發器taskDistributor提供執行緒池,該執行緒池為 normal 優先順序的無併發大小限制的執行緒池。

(3). createFileNameGenerator()

返回一個HashCodeFileNameGenerator物件,即以 uri HashCode 為檔名的檔名生成器。

(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)

建立一個 Disk Cache。如果 diskCacheSize 或者 diskCacheFileCount 大於 0,返回一個LruDiskCache,否則返回無大小限制的UnlimitedDiskCache

(5). createMemoryCache(Context context, int memoryCacheSize)

建立一個 Memory Cache。返回一個LruMemoryCache,若 memoryCacheSize 為 0,則設定該記憶體快取的最大位元組數為 App 最大可用記憶體的 1/8。
這裡 App 的最大可用記憶體也支援系統在 Honeycomb 之後(ApiLevel >= 11) application 中android:largeHeap="true"的設定。

(6). createImageDownloader(Context context)

建立圖片下載器,返回一個BaseImageDownloader

(7). createImageDecoder(boolean loggingEnabled)

建立圖片解碼器,返回一個BaseImageDecoder

(8). createBitmapDisplayer()

建立圖片顯示器,返回一個SimpleBitmapDisplayer

4.2.8 DefaultConfigurationFactory.DefaultThreadFactory

預設的執行緒工廠類,為
DefaultConfigurationFactory.createExecutor(…)

DefaultConfigurationFactory.createTaskDistributor(…)
提供執行緒工廠。支援執行緒優先順序設定,並且以固定規則命名新建的執行緒。

PS:重新命名執行緒是個很好的習慣,它的一大作用就是方便問題排查,比如效能優化,用 TraceView 檢視執行緒,根據名字很容易分辨各個執行緒。

4.2.9 ImageAware.java

需要顯示圖片的物件的介面,可包裝 View 表示某個需要顯示圖片的 View。

主要函式:

(1). View getWrappedView()

得到被包裝的 View,圖片在該 View 上顯示。

(2). getWidth() 與 getHeight()

得到寬度高度,在計算圖片縮放比例時會用到。

(3). getId()

得到唯一標識 id。ImageLoaderEngine中用這個 id 標識正在載入圖片的ImageAware和圖片記憶體快取 key 的對應關係,圖片請求前會將記憶體快取 key 與新的記憶體快取 key 進行比較,如果不相等,則之前的圖片請求會被取消。這樣當ImageAware被複用時就不會因非同步載入(前面任務未取消)而造成錯亂了。

4.2.10 ViewAware.java

封裝 Android View 來顯示圖片的抽象類,實現了ImageAware介面,利用Reference來 Warp View 防止記憶體洩露。

主要函式:

(1). ViewAware(View view, boolean checkActualViewSize)

建構函式。
view表示需要顯示圖片的物件。
checkActualViewSize表示通過getWidth()getHeight()獲取圖片寬高時返回真實的寬和高,還是LayoutParams的寬高,true 表示返回真實寬和高。
如果為true會導致一個問題,View在還沒有初始化完成時載入圖片,這時它的真實寬高為 0,會取它LayoutParams的寬高,而圖片快取的 key 與這個寬高有關,所以當View初始化完成再次需要載入該圖片時,getWidth()getHeight()返回的寬高都已經變化,快取 key 不一樣,從而導致快取命中失敗會再次從網路下載一次圖片。可通過ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()設定不允許記憶體快取快取一張圖片的多個尺寸。

(2). setImageDrawable(Drawable drawable)

如果當前操作在主執行緒並且 View 沒有被回收,則呼叫抽象函式setImageDrawableInto(Drawable drawable, View view)去向View設定圖片。

(3). setImageBitmap(Bitmap bitmap)

如果當前操作在主執行緒並且 View 沒有被回收,則呼叫抽象函式setImageBitmapInto(Bitmap bitmap, View view)去向View設定圖片。

4.2.11 ImageViewAware.java

封裝 Android ImageView 來顯示圖片的ImageAware,繼承了ViewAware,利用Reference來 Warp View 防止記憶體洩露。
如果getWidth()函式小於等於 0,會利用反射獲取mMaxWidth的值作為寬。
如果getHeight()函式小於等於 0,會利用反射獲取mMaxHeight的值作為高。

4.2.12 NonViewAware.java

僅包含處理圖片相關資訊卻沒有需要顯示圖片的 View 的ImageAware,實現了ImageAware介面。常用於載入圖片後呼叫回撥介面而不是顯示的情況。

4.2.13 DisplayImageOptions.java

圖片顯示的配置項。比如載入前、載入中、載入失敗應該顯示的佔點陣圖片,圖片是否需要在磁碟快取,是否需要在 memory 快取等。

主要屬性及含義:

(1). int imageResOnLoading

圖片正在載入中的佔點陣圖片的 resource id,優先順序比下面的imageOnLoading高,當存在時,imageOnLoading不起作用。

(2). int imageResForEmptyUri

空 uri 時的佔點陣圖片的 resource id,優先順序比下面的imageForEmptyUri高,當存在時,imageForEmptyUri不起作用。

(3). int imageResOnFail

載入失敗時的佔點陣圖片的 resource id,優先順序比下面的imageOnFail高,當存在時,imageOnFail不起作用。

(4). Drawable imageOnLoading

載入中的佔點陣圖片的 drawabled 物件,預設為 null。

(5). Drawable imageForEmptyUri

空 uri 時的佔點陣圖片的 drawabled 物件,預設為 null。

(6). Drawable imageOnFail

載入失敗時的佔點陣圖片的 drawabled 物件,預設為 null。

(7). boolean resetViewBeforeLoading

在載入前是否重置 view,通過 Builder 構建的物件預設為 false。

(8). boolean cacheInMemory

是否快取在記憶體中,通過 Builder 構建的物件預設為 false。

(9). boolean cacheOnDisk

是否快取在磁碟中,通過 Builder 構建的物件預設為 false。

(10). ImageScaleType imageScaleType

圖片的縮放型別,通過 Builder 構建的物件預設為IN_SAMPLE_POWER_OF_2

(11). Options decodingOptions;

為 BitmapFactory.Options,用於BitmapFactory.decodeStream(imageStream, null, decodingOptions)得到圖片尺寸等資訊。

(12). int delayBeforeLoading

設定在開始載入前的延遲時間,單位為毫秒,通過 Builder 構建的物件預設為 0。

(13). boolean considerExifParams

是否考慮圖片的 EXIF 資訊,通過 Builder 構建的物件預設為 false。

(14). Object extraForDownloader

下載器需要的輔助資訊。下載時傳入ImageDownloader.getStream(String, Object)的物件,方便使用者自己擴充套件,預設為 null。

(15). BitmapProcessor preProcessor

快取在記憶體之前的處理程式,預設為 null。

(16). BitmapProcessor postProcessor

快取在記憶體之後的處理程式,預設為 null。

(17). BitmapDisplayer displayer

圖片的顯示方式,通過 Builder 構建的物件預設為SimpleBitmapDisplayer

(18). Handler handler

handler 物件,預設為 null。

(19). boolean isSyncLoading

是否同步載入,通過 Builder 構建的物件預設為 false。

4.2.14 DisplayImageOptions.Builder.java 靜態內部類

Builder 模式,用於構造引數繁多的DisplayImageOptions
其屬性與DisplayImageOptions類似,函式多是屬性設定函式。

4.2.15 ImageLoadingListener.java

圖片載入各種時刻的回撥介面,可在圖片載入的某些點做監聽。
包括開始載入(onLoadingStarted)、載入失敗(onLoadingFailed)、載入成功(onLoadingComplete)、取消載入(onLoadingCancelled)四個回撥函式。

4.2.16 SimpleImageLoadingListener.java

實現ImageLoadingListener介面,不過各個函式都是空實現,表示不在 Image 載入過程中做任何回撥監聽。
ImageLoader.displayImage(…)函式中當入參listener為空時的預設值。

4.2.17 ImageLoadingProgressListener.java

Image 載入進度的回撥介面。其中抽象函式

void onProgressUpdate(String imageUri, View view, int current, int total)

會在獲取圖片儲存到檔案系統時被回撥。其中total表示圖片總大小,為網路請求結果Response Headercontent-length欄位,如果不存在則為 -1。

4.2.18 DisplayBitmapTask.java

顯示圖片的Task,實現了Runnable介面,必須在主執行緒呼叫。

主要函式:

(1) run()

首先判斷imageAware是否被 GC 回收,如果是直接呼叫取消載入回撥介面ImageLoadingListener.onLoadingCancelled(…)
否則判斷imageAware是否被複用,如果是直接呼叫取消載入回撥介面ImageLoadingListener.onLoadingCancelled(…)
否則呼叫displayer顯示圖片,並將imageAware從正在載入的 map 中移除。呼叫載入成功回撥介面ImageLoadingListener.onLoadingComplete(…)

對於 ListView 或是 GridView 這類會快取 Item 的 View 來說,單個 Item 中如果含有 ImageView,在滑動過程中可能因為非同步載入及 View 複用導致圖片錯亂,這裡對imageAware是否被複用的判斷就能很好的解決這個問題。原因類似:Android ListView 滑動過程中圖片顯示重複錯位閃爍問題原因及解決方案

4.2.19 ProcessAndDisplayImageTask.java

處理並顯示圖片的Task,實現了Runnable介面。

主要函式:

(1) run()

主要通過 imageLoadingInfo 得到BitmapProcessor處理圖片,並用處理後的圖片和配置新建一個DisplayBitmapTaskImageAware中顯示圖片。

4.2.20 LoadAndDisplayImageTask.java

載入並顯示圖片的Task,實現了Runnable介面,用於從網路、檔案系統或記憶體獲取圖片並解析,然後呼叫DisplayBitmapTaskImageAware中顯示圖片。

主要函式:

(1) run()

獲取圖片並顯示,核心程式碼如下:

bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
    bmp = tryLoadBitmap();
    ...
    ...
    ...
    if (bmp != null && options.isCacheInMemory()) {
        L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
        configuration.memoryCache.put(memoryCacheKey, bmp);
    }
}
……
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

從上面程式碼段中可以看到先是從記憶體快取中去讀取 bitmap 物件,若 bitmap 物件不存在,則呼叫 tryLoadBitmap() 函式獲取 bitmap 物件,獲取成功後若在 DisplayImageOptions.Builder 中設定了 cacheInMemory(true), 同時將 bitmap 物件快取到記憶體中。
最後新建DisplayBitmapTask顯示圖片。

函式流程圖如下:

Load and Display Image Task Flow Chart

  1. 判斷圖片的記憶體快取是否存在,若存在直接執行步驟 8;
  2. 判斷圖片的磁碟快取是否存在,若存在直接執行步驟 5;
  3. 從網路上下載圖片;
  4. 將圖片快取在磁碟上;
  5. 將圖片 decode 成 bitmap 物件;
  6. 根據DisplayImageOptions配置對圖片進行預處理(Pre-process Bitmap);
  7. 將 bitmap 物件快取到記憶體中;
  8. 根據DisplayImageOptions配置對圖片進行後處理(Post-process Bitmap);
  9. 執行DisplayBitmapTask將圖片顯示在相應的控制元件上。
    流程圖可以參見3. 流程圖
(2) tryLoadBitmap()

從磁碟快取或網路獲取圖片,核心程式碼如下:

File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
    ...
    bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
    ...
    String imageUriForDecoding = uri;
    if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
        imageFile = configuration.diskCache.get(uri);
        if (imageFile != null) {
            imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
        }
    }
    checkTaskNotActual();
    bitmap = decodeImage(imageUriForDecoding);
    ...
}

首先根據 uri 看看磁碟中是不是已經快取了這個檔案,如果已經快取,呼叫 decodeImage 函式,將圖片檔案 decode 成 bitmap 物件; 如果 bitmap 不合法或快取檔案不存在,判斷是否需要快取在磁碟,需要則呼叫tryCacheImageOnDisk()函式去下載並快取圖片到本地磁碟,再通過decodeImage(imageUri)函式將圖片檔案 decode 成 bitmap 物件,否則直接通過decodeImage(imageUriForDecoding)下載圖片並解析。

(3) tryCacheImageOnDisk()

下載圖片並儲存在磁碟內,根據磁碟快取圖片最長寬高的配置處理圖片。

    loaded = downloadImage();

主要就是這一句話,呼叫下載器下載並儲存圖片。
如果你在ImageLoaderConfiguration中還配置了maxImageWidthForDiskCache或者maxImageHeightForDiskCache,還會呼叫resizeAndSaveImage()函式,調整圖片尺寸,並儲存新的圖片檔案。

(4) downloadImage()

下載圖片並儲存在磁碟內。呼叫getDownloader()得到ImageDownloader去下載圖片。

(4) resizeAndSaveImage(int maxWidth, int maxHeight)

從磁碟快取中得到圖片,重新設定大小及進行一些處理後儲存。

(5) getDownloader()

根據ImageLoaderEngine配置得到下載器。
如果不允許訪問網路,則使用不允許訪問網路的圖片下載器NetworkDeniedImageDownloader;如果是慢網路情況,則使用慢網路情況下的圖片下載器SlowNetworkImageDownloader;否則直接使用ImageLoaderConfiguration中的downloader

4.2.21 ImageLoadingInfo.java

載入和顯示圖片任務需要的資訊。
String uri 圖片 url。
String memoryCacheKey 圖片快取 key。
ImageAware imageAware 需要載入圖片的物件。
ImageSize targetSize 圖片的顯示尺寸。
DisplayImageOptions options 圖片顯示的配置項。
ImageLoadingListener listener 圖片載入各種時刻的回撥介面。
ImageLoadingProgressListener progressListener 圖片載入進度的回撥介面。
ReentrantLock loadFromUriLock 圖片載入中的重入鎖。

4.2.22 ImageDownloader.java

圖片下載介面。待實現函式

getStream(String imageUri, Object extra)

表示通過 uri 得到 InputStream。
通過內部定義的列舉Scheme, 可以看出 UIL 支援哪些圖片來源。

HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
4.2.23 BaseImageDownloader.java

ImageDownloader的具體實現類。得到上面各種Scheme對應的圖片 InputStream。

主要函式

(1). getStream(String imageUri, Object extra)

getStream(…)函式內根據不同Scheme型別獲取圖片輸入流。

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            return getStreamFromNetwork(imageUri, extra);
        case FILE:
            return getStreamFromFile(imageUri, extra);
        case CONTENT:
            return getStreamFromContent(imageUri, extra);
        case ASSETS:
            return getStreamFromAssets(imageUri, extra);
        case DRAWABLE:
            return getStreamFromDrawable(imageUri, extra);
        case UNKNOWN:
        default:
            return getStreamFromOtherSource(imageUri, extra);
    }
}

具體見下面各函式介紹。

(2). getStreamFromNetwork(String imageUri, Object extra)

通過HttpURLConnection從網路獲取圖片的InputStream。支援 response code 為 3xx 的重定向。這裡有個小細節程式碼如下:

try {
    imageStream = conn.getInputStream();
} catch (IOException e) {
    // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
    IoUtils.readAndCloseStream(conn.getErrorStream());
    throw e;
}

在發生異常時會呼叫conn.getErrorStream()繼續讀取 Error Stream,這是為了利於網路連線回收及複用。但有意思的是在 Froyo(2.2) 之前,HttpURLConnection 有個重大 Bug,呼叫 close() 函式會影響連線池,導致連線複用失效,不少庫通過在 2.3 之前使用 AndroidHttpClient 解決這個問題。

(3). getStreamFromFile(String imageUri, Object extra)

從檔案系統獲取圖片的InputStream。如果 uri 是 video 型別,則需要單獨得到 video 的縮圖返回,否則按照一般讀取檔案操作返回。

(4). getStreamFromContent(String imageUri, Object extra)

從 ContentProvider 獲取圖片的InputStream
如果是 video 型別,則先從MediaStore得到 video 的縮圖返回;
如果是聯絡人型別,通過ContactsContract.Contacts.openContactPhotoInputStream(res, uri)讀取內容返回。
否則通過 ContentResolver.openInputStream(…) 讀取內容返回。

(5). getStreamFromAssets(String imageUri, Object extra)

從 Assets 中獲取圖片的InputStream

(6). getStreamFromDrawable(String imageUri, Object extra)

從 Drawable 資源中獲取圖片的InputStream

(7). getStreamFromOtherSource(String imageUri, Object extra)

UNKNOWN(自定義)型別的處理,目前是直接丟擲不支援的異常。

4.2.24 MemoryCache.java

Bitmap 記憶體快取介面,需要實現的介面包括 get(…)、put(…)、remove(…)、clear()、keys()。

4.2.25 BaseMemoryCache.java

實現了MemoryCache主要函式的抽象類,以 Map\<string, reference\> softMap 做為快取池,利於虛擬機器在記憶體不足時回收快取物件。提供抽象函式:

protected abstract Reference<Bitmap> createReference(Bitmap value)

表示根據 Bitmap 建立一個 Reference 做為快取物件。Reference 可以是 WeakReference、SoftReference 等。

4.2.26 WeakMemoryCache.java

WeakReference<Bitmap>做為快取 value 的記憶體快取,實現了BaseMemoryCache
實現了BaseMemoryCachecreateReference(Bitmap value)函式,直接返回一個new WeakReference<Bitmap>(value)做為快取 value。

4.2.27 LimitedMemoryCache.java

限制總位元組大小的記憶體快取,繼承自BaseMemoryCache的抽象類。
會在 put(…) 函式中判斷總體大小是否超出了上限,是則迴圈刪除快取物件直到小於上限。刪除順序由抽象函式

protected abstract Bitmap removeNext()

決定。抽象函式

protected abstract int getSize(Bitmap value)

表示每個元素大小。

4.2.28 LargestLimitedMemoryCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除 size 最大的元素,繼承自LimitedMemoryCache
實現了LimitedMemoryCache快取removeNext()函式,總是返回當前快取中 size 最大的元素。

4.2.29 UsingFreqLimitedMemoryCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除使用次數最少的元素,繼承自LimitedMemoryCache
實現了LimitedMemoryCache快取removeNext()函式,總是返回當前快取中使用次數最少的元素。

4.2.30 LRULimitedMemoryCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除最近最少使用的元素,繼承自LimitedMemoryCache
通過new LinkedHashMap<String, Bitmap>(10, 1.1f, true)作為快取池。LinkedHashMap 第三個參數列示是否需要根據訪問順序(accessOrder)排序,true 表示根據accessOrder排序,最近訪問的跟最新加入的一樣放到最後面,false 表示根據插入順序排序。這裡為 true 且快取滿時始終刪除第一個元素,即始終刪除最近最少訪問的元素。
實現了LimitedMemoryCache快取removeNext()函式,總是返回第一個元素,即最近最少使用的元素。

4.2.31 FIFOLimitedMemoryCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除先進入快取的元素,繼承自LimitedMemoryCache
實現了LimitedMemoryCache快取removeNext()函式,總是返回最先進入快取的元素。

以上所有LimitedMemoryCache子類都有個問題,就是 Bitmap 雖然通過WeakReference<Bitmap>包裝,但實際根本不會被虛擬機器回收,因為他們子類中同時都保留了 Bitmap 的強引用。大都是 UIL 早期實現的版本,不推薦使用。

4.2.32 LruMemoryCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除最近最少使用的元素,實現了MemoryCache。LRU(Least Recently Used) 為最近最少使用演算法。

new LinkedHashMap<String, Bitmap>(0, 0.75f, true)作為快取池。LinkedHashMap 第三個參數列示是否需要根據訪問順序(accessOrder)排序,true 表示根據accessOrder排序,最近訪問的跟最新加入的一樣放到最後面,false 表示根據插入順序排序。這裡為 true 且快取滿時始終刪除第一個元素,即始終刪除最近最少訪問的元素。

put(…)函式中通過trimToSize(int maxSize)函式判斷總體大小是否超出了上限,是則刪除第快取池中第一個元素,即最近最少使用的元素,直到總體大小小於上限。

LruMemoryCache功能上與LRULimitedMemoryCache類似,不過在實現上更加優雅。用簡單的實現介面方式,而不是不斷繼承的方式。

4.2.33 LimitedAgeMemoryCache.java

限制了物件最長存活週期的記憶體快取。
MemoryCache的裝飾者,相當於為MemoryCache新增了一個特性。以一個MemoryCache記憶體快取和一個 maxAge 做為建構函式入參。在 get(…) 時判斷如果物件存活時間已經超過設定的最長時間,則刪除。

4.2.34 FuzzyKeyMemoryCache.java

可以將某些原本不同的 key 看做相等,在 put 時刪除這些相等的 key。
MemoryCache的裝飾者,相當於為MemoryCache新增了一個特性。以一個MemoryCache記憶體快取和一個 keyComparator 做為建構函式入參。在 put(…) 時判斷如果 key 與快取中已有 key 經過Comparator比較後相等,則刪除之前的元素。

4.2.35 FileNameGenerator.java

根據 uri 得到檔名的介面。

4.2.36 HashCodeFileNameGenerator.java

以 uri 的 hashCode 作為檔名。

4.2.37 Md5FileNameGenerator.java

以 uri 的 MD5 值作為檔名。

4.2.38 DiskCache.java

圖片的磁碟快取介面。

主要函式:

(1) File get(String imageUri)

根據原始圖片的 uri 去獲取快取圖片的檔案。

(2) boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

儲存 imageStream 到磁碟中,listener 表示儲存進度且可在其中取消某些段的儲存。

(3) boolean save(String imageUri, Bitmap bitmap)

儲存圖片到磁碟。

(4) boolean remove(String imageUri)

根據圖片 uri 刪除快取圖片。

(5) void close()

關閉磁碟快取,並釋放資源。

(6) void clear()

清空磁碟快取。

(7) File getDirectory()

得到磁碟快取的根目錄。

4.2.39 BaseDiskCache.java

一個無大小限制的本地圖片快取,實現了DiskCache主要函式的抽象類。
圖片快取在cacheDir資料夾內,當cacheDir不可用時,則使用備庫reserveCacheDir

主要函式:

(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

先根據imageUri得到目標檔案,將imageStream先寫入與目標檔案同一資料夾的 .tmp 結尾的臨時檔案內,若未被listener取消且寫入成功則將臨時檔案重新命名為目標檔案並返回 true,否則刪除臨時檔案並返回 false。

(2). save(String imageUri, Bitmap bitmap)

先根據imageUri得到目標檔案,通過Bitmap.compress(…)函式將bitmap先寫入與目標檔案同一資料夾的 .tmp 結尾的臨時檔案內,若寫入成功則將臨時檔案重新命名為目標檔案並返回 true,否則刪除臨時檔案並返回 false。

(3). File getFile(String imageUri)

根據 imageUri 和 fileNameGenerator得到檔名,返回cacheDir內該檔案,若cacheDir不可用,則使用備庫reserveCacheDir

4.2.40 LimitedAgeDiskCache.java

限制了快取物件最長存活週期的磁碟快取,繼承自BaseDiskCache
在 get(…) 時判斷如果快取物件存活時間已經超過設定的最長時間,則刪除。在 save(…) 時儲存當存時間作為物件的建立時間。

4.2.41 UnlimitedDiskCache.java

一個無大小限制的本地圖片快取。與BaseDiskCache無異,只是用了個意思明確的類名。

4.2.42 DiskLruCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除最近最少使用的元素。

通過快取目錄下名為journal的檔案記錄快取的所有操作,並在快取open時讀取journal的檔案內容儲存到LinkedHashMap<String, Entry> lruEntries中,後面get(String key)獲取快取內容時,會先從lruEntries中得到圖片檔名返回檔案。

LRU 的實現跟上面記憶體快取類似,lruEntriesnew LinkedHashMap<String, Entry>(0, 0.75f, true),LinkedHashMap 第三個參數列示是否需要根據訪問順序(accessOrder)排序,true 表示根據accessOrder排序,最近訪問的跟最新加入的一樣放到最後面,false 表示根據插入順序排序。這裡為 true 且快取滿時trimToSize()函式始終刪除第一個元素,即始終刪除最近最少訪問的檔案。

來源於 JakeWharton 的開源專案 DiskLruCache,具體分析請等待 DiskLruCache 原始碼解析 完成。

4.2.43 LruDiskCache.java

限制總位元組大小的記憶體快取,會在快取滿時優先刪除最近最少使用的元素,實現了DiskCache
內部有個DiskLruCache cache屬性,快取的存、取操作基本都是由該屬性代理完成。

4.2.44 StrictLineReader.java

通過readLine()函式從InputStream中讀取一行,目前僅用於磁碟快取操作記錄檔案journal的解析。

4.2.45 Util.java

工具類。
String readFully(Reader reader)讀取 reader 中內容。
deleteContents(File dir)遞迴刪除資料夾內容。

4.2.46 ContentLengthInputStream.java

InputStream的裝飾者,可通過available()函式得到 InputStream 對應資料來源的長度(總位元組數)。主要用於計算檔案儲存進度即圖片下載進度時的總進度。

4.2.47 FailReason.java

圖片下載及顯示時的錯誤原因,目前包括:
IO_ERROR 網路連線或是磁碟儲存錯誤。
DECODING_ERROR decode image 為 Bitmap 時錯誤。
NETWORK_DENIED 當圖片不在快取中,且設定不允許訪問網路時的錯誤。
OUT_OF_MEMORY 記憶體溢位錯誤。
UNKNOWN 未知錯誤。

4.2.48 FlushedInputStream.java

為了解決早期 Android 版本BitmapFactory.decodeStream(…)在慢網路情況下 decode image 異常的 Bug。
主要通過重寫FilterInputStream的 skip(long n) 函式解決,確保 skip(long n) 始終跳過了 n 個位元組。如果返回結果即跳過的位元組數小於 n,則不斷迴圈直到 skip(long n) 跳過 n 位元組或到達檔案尾。

4.2.49 ImageScaleType.java

Image 的縮放型別,目前包括:
NONE不縮放。
NONE_SAFE根據需要以整數倍縮小圖片,使得其尺寸不超過 Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2根據需要以 2 的 n 次冪縮小圖片,使其尺寸不超過目標大小,比較快的縮小方式。
IN_SAMPLE_INT根據需要以整數倍縮小圖片,使其尺寸不超過目標大小。
EXACTLY根據需要縮小圖片到寬或高有一個與目標尺寸一致。
EXACTLY_STRETCHED根據需要縮放圖片到寬或高有一個與目標尺寸一致。

4.2.50 ViewScaleType.java

ImageAware的 ScaleType。
將 ImageView 的 ScaleType 簡化為兩種FIT_INSIDECROP兩種。FIT_INSIDE表示將圖片縮放到至少寬度和高度有一個小於等於 View 的對應尺寸,CROP表示將圖片縮放到寬度和高度都大於等於 View 的對應尺寸。

4.2.51 ImageSize.java

表示圖片寬高的類。
scaleDown(…) 等比縮小寬高。
scale(…) 等比放大寬高。

4.2.52 LoadedFrom.java

圖片來源列舉類,包括網路、磁碟快取、記憶體快取。

4.2.53 ImageDecoder.java

將圖片轉換為 Bitmap 的介面,抽象函式:

Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;

表示根據ImageDecodingInfo資訊得到圖片並根據引數將其轉換為 Bitmap。

4.2.54 BaseImageDecoder.java

實現了ImageDecoder。呼叫ImageDownloader獲取圖片,然後根據ImageDecodingInfo或圖片 Exif 資訊處理圖片轉換為 Bitmap。

主要函式:

(1). decode(ImageDecodingInfo decodingInfo)

呼叫ImageDownloader獲取圖片,再呼叫defineImageSizeAndRotation(…)函式得到圖片的相關資訊,呼叫prepareDecodingOptions(…)得到圖片縮放的比例,呼叫BitmapFactory.decodeStream將 InputStream 轉換為 Bitmap,最後呼叫considerExactScaleAndOrientatiton(…)根據引數將圖片放大、翻轉、旋轉為合適的樣子返回。

(2). defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)

得到圖片真實大小以及 Exif 資訊(設定考慮 Exif 的條件下)。

(3). defineExifOrientation(String imageUri)

得到圖片 Exif 資訊中的翻轉以及旋轉角度資訊。

(4). prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo)

得到圖片縮放的比例。

  1. 如果scaleType等於ImageScaleType.NONE,則縮放比例為 1;
  2. 如果scaleType等於ImageScaleType.NONE_SAFE,則縮放比例為(int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight))
  3. 否則,呼叫ImageSizeUtils.computeImageSampleSize(…)計算縮放比例。
    在 computeImageSampleSize(…) 中
  4. 如果viewScaleType等於ViewScaleType.FIT_INSIDE
    1.1 如果scaleType等於ImageScaleType.IN_SAMPLE_POWER_OF_2,則縮放比例從 1 開始不斷 *2 直到寬或高小於最大尺寸;
    1.2 否則取寬和高分別與最大尺寸比例中較大值,即Math.max(srcWidth / targetWidth, srcHeight / targetHeight)
  5. 如果scaleType等於ViewScaleType.CROP
    2.1 如果scaleType等於ImageScaleType.IN_SAMPLE_POWER_OF_2,則縮放比例從 1 開始不斷 *2 直到寬和高都小於最大尺寸。
    2.2 否則取寬和高分別與最大尺寸比例中較小值,即Math.min(srcWidth / targetWidth, srcHeight / targetHeight)
  6. 最後判斷寬和高是否超過最大值,如果是 *2 或是 +1 縮放。
(5). considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal)

根據引數將圖片放大、翻轉、旋轉為合適的樣子返回。

4.2.55 ImageDecodingInfo.java

Image Decode 需要的資訊。
String imageKey 圖片。
String imageUri 圖片 uri,可能是快取檔案的 uri。
String originalImageUri 圖片原 uri。
ImageSize targetSize 圖片的顯示尺寸。
imageScaleType 圖片的 ScaleType。
ImageDownloader downloader 圖片的下載器。
Object extraForDownloader 下載器需要的輔助資訊。
boolean considerExifParams 是否需要考慮圖片 Exif 資訊。
Options decodingOptions 圖片的解碼資訊,為 BitmapFactory.Options。

4.2.56 BitmapDisplayer.java

ImageAware中顯示 bitmap 物件的介面。可在實現中對 bitmap 做一些額外處理,比如加圓角、動畫效果。

4.2.57 FadeInBitmapDisplayer.java

圖片淡入方式顯示在ImageAware中,實現了BitmapDisplayer介面。

4.2.58 RoundedBitmapDisplayer.java

為圖片新增圓角顯示在ImageAware中,實現了BitmapDisplayer介面。主要通過BitmapShader實現。

4.2.59 RoundedVignetteBitmapDisplayer.java

為圖片新增漸變效果的圓角顯示在ImageAware中,實現了BitmapDisplayer介面。主要通過RadialGradient實現。

4.2.60 SimpleBitmapDisplayer.java

直接將圖片顯示在ImageAware中,實現了BitmapDisplayer介面。

4.2.61 BitmapProcessor.java

圖片處理介面。可用於對圖片預處理(Pre-process Bitmap)和後處理(Post-process Bitmap)。抽象函式:

public interface BitmapProcessor {
    Bitmap process(Bitmap bitmap);
}

使用者可以根據自己需求去實現它。比如你想要為你的圖片新增一個水印,那麼可以自己去實現 BitmapProcessor 介面,在DisplayImageOptions中配置 Pre-process 階段預處理圖片,這樣設定後儲存在檔案系統以及記憶體快取中的圖片都是加了水印後的。如果只希望在顯示時改變不動原圖片,可以在BitmapDisplayer中處理。

4.2.62 PauseOnScrollListener.java

可在 View 滾動過程中暫停圖片載入的 Listener,實現了 OnScrollListener 介面。
它的好處是防止滾動中不必要的圖片載入,比如快速滾動不希望滾動中的圖片載入。在 ListView 或 GridView 中 item 載入圖片最好使用它,簡單的一行程式碼:

gridView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));

主要的成員變數:
pauseOnScroll 觸控滑動(手指依然在螢幕上)過程中是否暫停圖片載入。
pauseOnFling 甩指滾動(手指已離開螢幕)過程中是否暫停圖片載入。
externalListener 自定義的 OnScrollListener 介面,適用於 View 原來就有自定義 OnScrollListener 情況設定。

實現原理:
重寫onScrollStateChanged(…)函式判斷不同的狀態下暫停或繼續圖片載入。
OnScrollListener.SCROLL_STATE_IDLE表示 View 處於空閒狀態,沒有在滾動,這時候會載入圖片。
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL表示 View 處於觸控滑動狀態,手指依然在螢幕上,通過pauseOnScroll變數確定是否需要暫停圖片載入。這種時候大都屬於慢速滾動瀏覽狀態,所以建議繼續圖片載入。
OnScrollListener.SCROLL_STATE_FLING表示 View 處於甩指滾動狀態,手指已離開螢幕,通過pauseOnFling變數確定是否需要暫停圖片載入。這種時候大都屬於快速滾動狀態,所以建議暫停圖片載入以節省資源。

4.2.63 QueueProcessingType.java

任務佇列的處理型別,包括FIFO先進先出、LIFO後進先出。

4.2.64 LIFOLinkedBlockingDeque.java

後進先出阻塞佇列。重寫LinkedBlockingDequeoffer(…)函式如下:

@Override
public boolean offer(T e) {
    return super.offerFirst(e);
}

LinkedBlockingDeque插入總在最前,而remove()本身始終刪除第一個元素,所以就變為了後進先出阻塞佇列。
實際一般情況只重寫offer(…)函式是不夠的,但因為ThreadPoolExecutor預設只用到了BlockingQueueoffer(…)函式,所以這種簡單重寫後做為ThreadPoolExecutor的任務佇列沒問題。

LIFOLinkedBlockingDeque.java包下的LinkedBlockingDeque.javaBlockingDeque.javaDeque.java都是 Java 1.6 原始碼中的,這裡不做分析。

4.2.65 DiskCacheUtils.java

磁碟快取工具類,可用於查詢或刪除某個 uri 對應的磁碟快取。

4.2.66 MemoryCacheUtils.java

記憶體快取工具類。可用於根據 uri 生成記憶體快取 key,快取 key 比較,根據 uri 得到所有相關的 key 或圖片,刪除某個 uri 的記憶體快取。
generateKey(String imageUri, ImageSize targetSize)
根據 uri 生成記憶體快取 key,key 規則為[imageUri]_[width]x[height]

4.2.67 StorageUtils.java

得到圖片 SD 卡快取目錄路徑。
快取目錄優先選擇/Android/data/[app_package_name]/cache;若無許可權或不可用,則選擇 App 在檔案系統的快取目錄context.getCacheDir();若無許可權或不可用,則選擇/data/data/[app_package_name]/cache

如果快取目錄選擇了/Android/data/[app_package_name]/cache,則新建.nomedia檔案表示不允許類似 Galley 這些應用顯示此資料夾下圖片。不過在 4.0 系統有 Bug 這種方式不生效。

4.2.68 ImageSizeUtils.java

用於計算圖片尺寸、縮放比例相關的工具類。

4.2.69 IoUtils.java

IO 相關工具類,包括 stream 拷貝,關閉等。

4.2.70 L.java

Log 工具類。

5. 雜談

聊聊 LRU

UIL 的記憶體快取預設使用了 LRU 演算法。 LRU: Least Recently Used 近期最少使用演算法, 選用了基於連結串列結構的 LinkedHashMap 作為儲存結構。
假設情景:記憶體快取設定的閾值只夠儲存兩個 bitmap 物件,當 put 第三個 bitmap 物件時,將近期最少使用的 bitmap 物件移除。
圖 1: 初始化 LinkedHashMap, 並按使用順序來排序, accessOrder = true;
圖 2: 向快取池中放入 bitmap1 和 bitmap2 兩個物件。
圖 3: 繼續放入第三個 bitmap3,根據假設情景,將會超過設定快取池閾值。
圖 4: 釋放對 bitmap1 物件的引用。
圖 5: bitmap1 物件被 GC 回收。






相關文章