寫(xiě)在前面,UIL這個(gè)圖片加載框架在去年的時(shí)候,作者就已經(jīng)宣布因?yàn)闀r(shí)間關(guān)系,停止維護(hù)了!
以下為作者原話:
Project News
- Really have no time for development... so I stop project maintaining since Nov - 27 :(
- UIL [27.11.2011 - 27.11.2015]
- Thanks to all developers for your support :)
本文為 Android 開(kāi)源項(xiàng)目源碼解析 中 Android Universal Image Loader 部分項(xiàng)目地址:Android-Universal-Image-Loader,分析的版本:eb794c3,Demo 地址:UIL Demo分析者:huxian99,校對(duì)者:Grumoon、Trinea,校對(duì)狀態(tài):完成
1. 功能介紹
1.1 Android Universal Image Loader
Android Universal Image Loader 是一個(gè)強(qiáng)大的、可高度定制的圖片緩存,本文簡(jiǎn)稱為UIL。
簡(jiǎn)單的說(shuō) UIL 就做了一件事——獲取圖片并顯示在相應(yīng)的控件上。
1.2 基本使用
1.2.1 初始化
添加完依賴后在Application或Activity中初始化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>
添加網(wǎng)絡(luò)權(quán)限。如果允許磁盤緩存,需要添加寫(xiě)外設(shè)的權(quán)限。
1.2.3 下載顯示圖片
下載圖片,解析為 Bitmap 并在 ImageView 中顯示。
imageLoader.displayImage(imageUri, imageView);
下載圖片,解析為 Bitmap 傳遞給回調(diào)接口。
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// 圖片處理
}
});
以上是簡(jiǎn)單使用,更復(fù)雜 API 見(jiàn)本文詳細(xì)設(shè)計(jì)。
1.3 特點(diǎn)
可配置度高。支持任務(wù)線程池、下載器、解碼器、內(nèi)存及磁盤緩存、顯示選項(xiàng)等等的配置。
包含內(nèi)存緩存和磁盤緩存兩級(jí)緩存。
支持多線程,支持異步和同步加載。
支持多種緩存算法、下載進(jìn)度監(jiān)聽(tīng)、ListView 圖片錯(cuò)亂解決等。
2. 總體設(shè)計(jì)
2.1. 總體設(shè)計(jì)圖

上面是 UIL 的總體設(shè)計(jì)圖。整個(gè)庫(kù)分為ImageLoaderEngine,Cache及ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor五大模塊,其中Cache分為MemoryCache和DiskCache兩部分。
簡(jiǎn)單的講就是ImageLoader收到加載及顯示圖片的任務(wù),并將它交給ImageLoaderEngine,ImageLoaderEngine分發(fā)任務(wù)到具體線程池去執(zhí)行,任務(wù)通過(guò)Cache及ImageDownloader獲取圖片,中間可能經(jīng)過(guò)BitmapProcessor和ImageDecoder處理,最終轉(zhuǎn)換為Bitmap交給BitmapDisplayer在ImageAware中顯示。
2.2. UIL 中的概念
簡(jiǎn)單介紹一些概念,在4. 詳細(xì)設(shè)計(jì)中會(huì)仔細(xì)介紹。
ImageLoaderEngine:任務(wù)分發(fā)器,負(fù)責(zé)分發(fā)LoadAndDisplayImageTask和ProcessAndDisplayImageTask給具體的線程池去執(zhí)行,本文中也稱其為engine,具體參考4.2.6 ImageLoaderEngine.java。
ImageAware:顯示圖片的對(duì)象,可以是ImageView等,具體參考4.2.9 ImageAware.java。
ImageDownloader:圖片下載器,負(fù)責(zé)從圖片的各個(gè)來(lái)源獲取輸入流, 具體參考4.2.22 ImageDownloader.java。
Cache:圖片緩存,分為MemoryCache和DiskCache兩部分。
MemoryCache:內(nèi)存圖片緩存,可向內(nèi)存緩存緩存圖片或從內(nèi)存緩存讀取圖片,具體參考4.2.24 MemoryCache.java。
DiskCache:本地圖片緩存,可向本地磁盤緩存保存圖片或從本地磁盤讀取圖片,具體參考4.2.38 DiskCache.java。
ImageDecoder:圖片解碼器,負(fù)責(zé)將圖片輸入流InputStream轉(zhuǎn)換為Bitmap對(duì)象, 具體參考4.2.53 ImageDecoder.java。
BitmapProcessor:圖片處理器,負(fù)責(zé)從緩存讀取或?qū)懭肭皩?duì)圖片進(jìn)行處理。具體參考4.2.61 BitmapProcessor.java。
BitmapDisplayer:將Bitmap對(duì)象顯示在相應(yīng)的控件ImageAware上, 具體參考4.2.56 BitmapDisplayer.java。
LoadAndDisplayImageTask:用于加載并顯示圖片的任務(wù), 具體參考4.2.20 LoadAndDisplayImageTask.java。
ProcessAndDisplayImageTask:用于處理并顯示圖片的任務(wù), 具體參考4.2.19 ProcessAndDisplayImageTask.java。
DisplayBitmapTask:用于顯示圖片的任務(wù), 具體參考4.2.18 DisplayBitmapTask.java。
3. 流程圖

上圖為圖片加載及顯示流程圖,在 uil 庫(kù)中給出,這里用中文重新畫(huà)出。
4. 詳細(xì)設(shè)計(jì)
4.1 類關(guān)系圖

4.2 核心類功能介紹
4.2.1 ImageLoader.java
圖片加載器,對(duì)外的主要 API,采取了單例模式,用于圖片的加載和顯示。
主要函數(shù):
(1). getInstance()
得到ImageLoader的單例。通過(guò)雙層是否為 null 判斷提高性能。
(2). init(ImageLoaderConfiguration configuration)
初始化配置參數(shù),參數(shù)configuration為ImageLoader的配置信息,包括圖片最大尺寸、任務(wù)線程池、磁盤緩存、下載器、解碼器等等。
實(shí)現(xiàn)中會(huì)初始化ImageLoaderEngine engine屬性,該屬性為任務(wù)分發(fā)器。
(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
加載并顯示圖片或加載并執(zhí)行回調(diào)接口。
ImageLoader 加載圖片主要分為三類接口:
displayImage(…) 表示異步加載并顯示圖片到對(duì)應(yīng)的ImageAware上。
loadImage(…) 表示異步加載圖片并執(zhí)行回調(diào)接口。
loadImageSync(…) 表示同步加載圖片。
以上三類接口最終都會(huì)調(diào)用到這個(gè)函數(shù)進(jìn)行圖片加載。函數(shù)參數(shù)解釋如下:
uri: 圖片的 uri。uri 支持多種來(lái)源的圖片,包括 http、https、file、content、assets、drawable 及自定義,具體介紹可見(jiàn)ImageDownloader。
imageAware: 一個(gè)接口,表示需要加載圖片的對(duì)象,可包裝 View。
options: 圖片顯示的配置項(xiàng)。比如加載前、加載中、加載失敗應(yīng)該顯示的占位圖片,圖片是否需要在磁盤緩存,是否需要在內(nèi)存緩存等。
listener: 圖片加載各種時(shí)刻的回調(diào)接口,包括開(kāi)始加載、加載失敗、加載成功、取消加載四個(gè)時(shí)刻的回調(diào)函數(shù)。
progressListener: 圖片加載進(jìn)度的回調(diào)接口。
函數(shù)流程圖如下:

ImageLoader Display Image Flow Chart
4.2.2 ImageLoaderConfiguration.java
ImageLoader的配置信息,包括圖片最大尺寸、線程池、緩存、下載器、解碼器等等。
主要屬性:
(1). Resources resources
程序本地資源訪問(wèn)器,用于加載DisplayImageOptions中設(shè)置的一些 App 中圖片資源。
(2). int maxImageWidthForMemoryCache
內(nèi)存緩存的圖片最大寬度。
(3). int maxImageHeightForMemoryCache
內(nèi)存緩存的圖片最大高度。
(4). int maxImageWidthForDiskCache
磁盤緩存的圖片最大寬度。
(5). int maxImageHeightForDiskCache
磁盤緩存的圖片最大高度。
(6). BitmapProcessor processorForDiskCache
圖片處理器,用于處理從磁盤緩存中讀取到的圖片。
(7). Executor taskExecutor
ImageLoaderEngine中用于執(zhí)行從源獲取圖片任務(wù)的 Executor。
(18). Executor taskExecutorForCachedImages
ImageLoaderEngine中用于執(zhí)行從緩存獲取圖片任務(wù)的 Executor。
(19). boolean customExecutor
用戶是否自定義了上面的 taskExecutor。
(20). boolean customExecutorForCachedImages
用戶是否自定義了上面的 taskExecutorForCachedImages。
(21). int threadPoolSize
上面兩個(gè)默認(rèn)線程池的核心池大小,即最大并發(fā)數(shù)。
(22). int threadPriority
上面兩個(gè)默認(rèn)線程池的線程優(yōu)先級(jí)。
(23). QueueProcessingType tasksProcessingType
上面兩個(gè)默認(rèn)線程池的線程隊(duì)列類型。目前只有 FIFO, LIFO 兩種可供選擇。
(24). MemoryCache memoryCache
圖片內(nèi)存緩存。
(25). DiskCache diskCache
圖片磁盤緩存,一般放在 SD 卡。
(26). ImageDownloader downloader
圖片下載器。
(27). ImageDecoder decoder
圖片解碼器,內(nèi)部可使用我們常用的BitmapFactory.decode(…)將圖片資源解碼成Bitmap對(duì)象。
(28). DisplayImageOptions defaultDisplayImageOptions
圖片顯示的配置項(xiàng)。比如加載前、加載中、加載失敗應(yīng)該顯示的占位圖片,圖片是否需要在磁盤緩存,是否需要在內(nèi)存緩存等。
(29). ImageDownloader networkDeniedDownloader
不允許訪問(wèn)網(wǎng)絡(luò)的圖片下載器。
(30). ImageDownloader slowNetworkDownloader
慢網(wǎng)絡(luò)情況下的圖片下載器。
4.2.3 ImageLoaderConfiguration.Builder.java 靜態(tài)內(nèi)部類
Builder 模式,用于構(gòu)造參數(shù)繁多的ImageLoaderConfiguration。
其屬性與ImageLoaderConfiguration類似,函數(shù)多是屬性設(shè)置函數(shù)。
主要函數(shù)及含義:
(1). build()
按照配置,生成 ImageLoaderConfiguration。代碼如下:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
(2). initEmptyFieldsWithDefaultValues()
初始化值為null的屬性。若用戶沒(méi)有配置相關(guān)項(xiàng),UIL 會(huì)通過(guò)調(diào)用DefaultConfigurationFactory中的函數(shù)返回一個(gè)默認(rèn)值當(dāng)配置。
taskExecutorForCachedImages、taskExecutor及ImageLoaderEngine的taskDistributor的默認(rèn)值如下:
| parameters | taskDistributor | taskExecutorForCachedImages/taskExecutor |
|---|---|---|
| corePoolSize | 0 | 3 |
| maximumPoolSize | Integer.MAX_VALUE | 3 |
| keepAliveTime | 60 | 0 |
| unit | SECONDS | MILLISECONDS |
| workQueue | ynchronousQueue | LIFOLinkedBlockingDeque / LinkedBlockingQueue |
| priority | 5 | 3 |
diskCacheFileNameGenerator默認(rèn)值為HashCodeFileNameGenerator。
memoryCache默認(rèn)值為L(zhǎng)ruMemoryCache。如果內(nèi)存緩存不允許緩存一張圖片的多個(gè)尺寸,則用FuzzyKeyMemoryCache做封裝,同一個(gè)圖片新的尺寸會(huì)覆蓋緩存中該圖片老的尺寸。
diskCache默認(rèn)值與diskCacheSize和diskCacheFileCount值有關(guān),如果他們有一個(gè)大于 0,則默認(rèn)為L(zhǎng)ruDiskCache,否則使用無(wú)大小限制的UnlimitedDiskCache。
downloader默認(rèn)值為BaseImageDownloader。
decoder默認(rèn)值為BaseImageDecoder。
詳細(xì)及其他屬性默認(rèn)值請(qǐng)到DefaultConfigurationFactory中查看。
(3). denyCacheImageMultipleSizesInMemory()
設(shè)置內(nèi)存緩存不允許緩存一張圖片的多個(gè)尺寸,默認(rèn)允許。
后面會(huì)講到 View 的 getWidth() 在初始化前后的不同值與這個(gè)設(shè)置的關(guān)系。
(4). diskCacheSize(int maxCacheSize)
設(shè)置磁盤緩存的最大字節(jié)數(shù),如果大于 0 或者下面的maxFileCount大于 0,默認(rèn)的DiskCache會(huì)用LruDiskCache,否則使用無(wú)大小限制的UnlimitedDiskCache。
(5). diskCacheFileCount(int maxFileCount)
設(shè)置磁盤緩存文件夾下最大文件數(shù),如果大于 0 或者上面的maxCacheSize大于 0,默認(rèn)的DiskCache會(huì)用LruDiskCache,否則使用無(wú)大小限制的UnlimitedDiskCache。
4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 靜態(tài)內(nèi)部類
不允許訪問(wèn)網(wǎng)絡(luò)的圖片下載器,實(shí)現(xiàn)了ImageDownloader接口。
實(shí)現(xiàn)也比較簡(jiǎn)單,包裝一個(gè)ImageDownloader對(duì)象,通過(guò)在 getStream(…) 函數(shù)中禁止 Http 和 Https Scheme 禁止網(wǎng)絡(luò)訪問(wèn),如下:
@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 靜態(tài)內(nèi)部類
慢網(wǎng)絡(luò)情況下的圖片下載器,實(shí)現(xiàn)了ImageDownloader接口。
通過(guò)包裝一個(gè)ImageDownloader對(duì)象實(shí)現(xiàn),在 getStream(…) 函數(shù)中當(dāng) Scheme 為 Http 和 Https 時(shí),用FlushedInputStream代替InputStream處理慢網(wǎng)絡(luò)情況,具體見(jiàn)后面FlushedInputStream的介紹。
4.2.6 ImageLoaderEngine.java
LoadAndDisplayImageTask和ProcessAndDisplayImageTask任務(wù)分發(fā)器,負(fù)責(zé)分發(fā)任務(wù)給具體的線程池。
主要屬性:
(1). ImageLoaderConfiguration configuration
ImageLoader的配置信息,可包括圖片最大尺寸、線程池、緩存、下載器、解碼器等等。
(2). Executor taskExecutor
用于執(zhí)行從源獲取圖片任務(wù)的 Executor,為configuration中的 taskExecutor,如果為null,則會(huì)調(diào)用DefaultConfigurationFactory.createExecutor(…)根據(jù)配置返回一個(gè)默認(rèn)的線程池。
(3). Executor taskExecutorForCachedImages
用于執(zhí)行從緩存獲取圖片任務(wù)的 Executor,為configuration中的 taskExecutorForCachedImages,如果為null,則會(huì)調(diào)用DefaultConfigurationFactory.createExecutor(…)根據(jù)配置返回一個(gè)默認(rèn)的線程池。
(4). Executor taskDistributor
任務(wù)分發(fā)線程池,任務(wù)指LoadAndDisplayImageTask和ProcessAndDisplayImageTask,因?yàn)橹恍枰职l(fā)給上面的兩個(gè) Executor 去執(zhí)行任務(wù),不存在較耗時(shí)或阻塞操作,所以用無(wú)并發(fā)數(shù)(Int 最大值)限制的線程池即可。
(5). Map cacheKeysForImageAwares
ImageAware與內(nèi)存緩存 key 對(duì)應(yīng)的 map,key 為ImageAware的 id,value 為內(nèi)存緩存的 key。
(6). Map uriLocks
圖片正在加載的重入鎖 map,key 為圖片的 uri,value 為標(biāo)識(shí)其正在加載的重入鎖。
(7). AtomicBoolean paused
是否被暫停。如果為true,則所有新的加載或顯示任務(wù)都會(huì)等待直到取消暫停(為false)。
(8). AtomicBoolean networkDenied
是否不允許訪問(wèn)網(wǎng)絡(luò),如果為true,通過(guò)ImageLoadingListener.onLoadingFailed(…)獲取圖片,則所有不在緩存中需要網(wǎng)絡(luò)訪問(wèn)的請(qǐng)求都會(huì)失敗,返回失敗原因?yàn)榫W(wǎng)絡(luò)訪問(wèn)被禁止。
(9). AtomicBoolean slowNetwork
是否是慢網(wǎng)絡(luò)情況,如果為true,則自動(dòng)調(diào)用SlowNetworkImageDownloader下載圖片。
(10). Object pauseLock
暫停的等待鎖,可在engine被暫停后調(diào)用這個(gè)鎖等待。
主要函數(shù):
(1). void submit(final LoadAndDisplayImageTask task)
添加一個(gè)LoadAndDisplayImageTask。直接用taskDistributor執(zhí)行一個(gè) Runnable,在 Runnable 內(nèi)部根據(jù)圖片是否被磁盤緩存過(guò)確定使用taskExecutorForCachedImages還是taskExecutor執(zhí)行該 task。
(2). void submit(ProcessAndDisplayImageTask task)
添加一個(gè)ProcessAndDisplayImageTask。直接用taskExecutorForCachedImages執(zhí)行該 task。
(3). void pause()
暫停圖片加載任務(wù)。所有新的加載或顯示任務(wù)都會(huì)等待直到取消暫停(為false)。
(4). void resume()
繼續(xù)圖片加載任務(wù)。
(5). stop()
暫停所有加載和顯示圖片任務(wù)并清除這里的內(nèi)部屬性值。
(6). fireCallback(Runnable r)
taskDistributor立即執(zhí)行某個(gè)任務(wù)。
(7). getLockForUri(String uri)
得到某個(gè) uri 的重入鎖,如果不存在則新建。
(8). createTaskExecutor()
調(diào)用DefaultConfigurationFactory.createExecutor(…)創(chuàng)建一個(gè)線程池。
(9). getLoadingUriForView(ImageAware imageAware)
得到某個(gè)imageAware正在加載的圖片 uri。
(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)
準(zhǔn)備開(kāi)始一個(gè)Task。向cacheKeysForImageAwares中插入ImageAware的 id 和圖片在內(nèi)存緩存中的 key。
(11). void cancelDisplayTaskFor(ImageAware imageAware)
取消一個(gè)顯示任務(wù)。從cacheKeysForImageAwares中刪除ImageAware對(duì)應(yīng)元素。
(12). denyNetworkDownloads(boolean denyNetworkDownloads)
設(shè)置是否不允許網(wǎng)絡(luò)訪問(wèn)。
(13). handleSlowNetwork(boolean handleSlowNetwork)
設(shè)置是否慢網(wǎng)絡(luò)情況。
4.2.7 DefaultConfigurationFactory.java
為ImageLoaderConfiguration及ImageLoaderEngine提供一些默認(rèn)配置。
主要函數(shù):
(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)
創(chuàng)建線程池。
threadPoolSize表示核心池大小(最大并發(fā)數(shù))。
threadPriority表示線程優(yōu)先級(jí)。
tasksProcessingType表示線程隊(duì)列類型,目前只有 FIFO, LIFO 兩種可供選擇。
內(nèi)部實(shí)現(xiàn)會(huì)調(diào)用createThreadFactory(…)返回一個(gè)支持線程優(yōu)先級(jí)設(shè)置,并且以固定規(guī)則命名新建的線程的線程工廠類DefaultConfigurationFactory.DefaultThreadFactory。
(2). createTaskDistributor()
為ImageLoaderEngine中的任務(wù)分發(fā)器taskDistributor提供線程池,該線程池為 normal 優(yōu)先級(jí)的無(wú)并發(fā)大小限制的線程池。
(3). createFileNameGenerator()
返回一個(gè)HashCodeFileNameGenerator對(duì)象,即以 uri HashCode 為文件名的文件名生成器。
(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)
創(chuàng)建一個(gè) Disk Cache。如果 diskCacheSize 或者 diskCacheFileCount 大于 0,返回一個(gè)LruDiskCache,否則返回?zé)o大小限制的UnlimitedDiskCache。
(5). createMemoryCache(Context context, int memoryCacheSize)
創(chuàng)建一個(gè) Memory Cache。返回一個(gè)LruMemoryCache,若 memoryCacheSize 為 0,則設(shè)置該內(nèi)存緩存的最大字節(jié)數(shù)為 App 最大可用內(nèi)存的 1/8。
這里 App 的最大可用內(nèi)存也支持系統(tǒng)在 Honeycomb 之后(ApiLevel >= 11) application 中android:largeHeap="true"的設(shè)置。
(6). createImageDownloader(Context context)
創(chuàng)建圖片下載器,返回一個(gè)BaseImageDownloader。
(7). createImageDecoder(boolean loggingEnabled)
創(chuàng)建圖片解碼器,返回一個(gè)BaseImageDecoder。
(8). createBitmapDisplayer()
創(chuàng)建圖片顯示器,返回一個(gè)SimpleBitmapDisplayer。
4.2.8 DefaultConfigurationFactory.DefaultThreadFactory
默認(rèn)的線程工廠類,為DefaultConfigurationFactory.createExecutor(…)和DefaultConfigurationFactory.createTaskDistributor(…)提供線程工廠。支持線程優(yōu)先級(jí)設(shè)置,并且以固定規(guī)則命名新建的線程。
PS:重命名線程是個(gè)很好的習(xí)慣,它的一大作用就是方便問(wèn)題排查,比如性能優(yōu)化,用 TraceView 查看線程,根據(jù)名字很容易分辨各個(gè)線程。
4.2.9 ImageAware.java
需要顯示圖片的對(duì)象的接口,可包裝 View 表示某個(gè)需要顯示圖片的 View。
主要函數(shù):
(1). View getWrappedView()
得到被包裝的 View,圖片在該 View 上顯示。
(2). getWidth() 與 getHeight()
得到寬度高度,在計(jì)算圖片縮放比例時(shí)會(huì)用到。
(3). getId()
得到唯一標(biāo)識(shí) id。ImageLoaderEngine中用這個(gè) id 標(biāo)識(shí)正在加載圖片的ImageAware和圖片內(nèi)存緩存 key 的對(duì)應(yīng)關(guān)系,圖片請(qǐng)求前會(huì)將內(nèi)存緩存 key 與新的內(nèi)存緩存 key 進(jìn)行比較,如果不相等,則之前的圖片請(qǐng)求會(huì)被取消。這樣當(dāng)ImageAware被復(fù)用時(shí)就不會(huì)因異步加載(前面任務(wù)未取消)而造成錯(cuò)亂了。
4.2.10 ViewAware.java
封裝 Android View 來(lái)顯示圖片的抽象類,實(shí)現(xiàn)了ImageAware接口,利用Reference來(lái) Warp View 防止內(nèi)存泄露。
主要函數(shù):
(1). ViewAware(View view, boolean checkActualViewSize)
構(gòu)造函數(shù)。
view表示需要顯示圖片的對(duì)象。
checkActualViewSize表示通過(guò)getWidth()和getHeight()獲取圖片寬高時(shí)返回真實(shí)的寬和高,還是LayoutParams的寬高,true 表示返回真實(shí)寬和高。
如果為true會(huì)導(dǎo)致一個(gè)問(wèn)題,View在還沒(méi)有初始化完成時(shí)加載圖片,這時(shí)它的真實(shí)寬高為 0,會(huì)取它LayoutParams的寬高,而圖片緩存的 key 與這個(gè)寬高有關(guān),所以當(dāng)View初始化完成再次需要加載該圖片時(shí),getWidth()和getHeight()返回的寬高都已經(jīng)變化,緩存 key 不一樣,從而導(dǎo)致緩存命中失敗會(huì)再次從網(wǎng)絡(luò)下載一次圖片。可通過(guò)ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()設(shè)置不允許內(nèi)存緩存緩存一張圖片的多個(gè)尺寸。
(2). setImageDrawable(Drawable drawable)
如果當(dāng)前操作在主線程并且 View 沒(méi)有被回收,則調(diào)用抽象函數(shù)setImageDrawableInto(Drawable drawable, View view)去向View設(shè)置圖片。
(3). setImageBitmap(Bitmap bitmap)
如果當(dāng)前操作在主線程并且 View 沒(méi)有被回收,則調(diào)用抽象函數(shù)setImageBitmapInto(Bitmap bitmap, View view)去向View設(shè)置圖片。
4.2.11 ImageViewAware.java
封裝 Android ImageView 來(lái)顯示圖片的ImageAware,繼承了ViewAware,利用Reference來(lái) Warp View 防止內(nèi)存泄露。
如果getWidth()函數(shù)小于等于 0,會(huì)利用反射獲取mMaxWidth的值作為寬。
如果getHeight()函數(shù)小于等于 0,會(huì)利用反射獲取mMaxHeight的值作為高。
4.2.12 NonViewAware.java
僅包含處理圖片相關(guān)信息卻沒(méi)有需要顯示圖片的 View 的ImageAware,實(shí)現(xiàn)了ImageAware接口。常用于加載圖片后調(diào)用回調(diào)接口而不是顯示的情況。
4.2.13 DisplayImageOptions.java
圖片顯示的配置項(xiàng)。比如加載前、加載中、加載失敗應(yīng)該顯示的占位圖片,圖片是否需要在磁盤緩存,是否需要在 memory 緩存等。
主要屬性及含義:
(1). int imageResOnLoading
圖片正在加載中的占位圖片的 resource id,優(yōu)先級(jí)比下面的imageOnLoading高,當(dāng)存在時(shí),imageOnLoading不起作用。
(2). int imageResForEmptyUri
空 uri 時(shí)的占位圖片的 resource id,優(yōu)先級(jí)比下面的imageForEmptyUri高,當(dāng)存在時(shí),imageForEmptyUri不起作用。
(3). int imageResOnFail
加載失敗時(shí)的占位圖片的 resource id,優(yōu)先級(jí)比下面的imageOnFail高,當(dāng)存在時(shí),imageOnFail不起作用。
(4). Drawable imageOnLoading
加載中的占位圖片的 drawabled 對(duì)象,默認(rèn)為 null。
(5). Drawable imageForEmptyUri
空 uri 時(shí)的占位圖片的 drawabled 對(duì)象,默認(rèn)為 null。
(6). Drawable imageOnFail
加載失敗時(shí)的占位圖片的 drawabled 對(duì)象,默認(rèn)為 null。
(7). boolean resetViewBeforeLoading
在加載前是否重置 view,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 false。
(8). boolean cacheInMemory
是否緩存在內(nèi)存中,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 false。
(9). boolean cacheOnDisk
是否緩存在磁盤中,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 false。
(10). ImageScaleType imageScaleType
圖片的縮放類型,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為IN_SAMPLE_POWER_OF_2。
(11). Options decodingOptions;
為 BitmapFactory.Options,用于BitmapFactory.decodeStream(imageStream, null, decodingOptions)得到圖片尺寸等信息。
(12). int delayBeforeLoading
設(shè)置在開(kāi)始加載前的延遲時(shí)間,單位為毫秒,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 0。
(13). boolean considerExifParams
是否考慮圖片的 EXIF 信息,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 false。
(14). Object extraForDownloader
下載器需要的輔助信息。下載時(shí)傳入ImageDownloader.getStream(String, Object)的對(duì)象,方便用戶自己擴(kuò)展,默認(rèn)為 null。
(15). BitmapProcessor preProcessor
緩存在內(nèi)存之前的處理程序,默認(rèn)為 null。
(16). BitmapProcessor postProcessor
緩存在內(nèi)存之后的處理程序,默認(rèn)為 null。
(17). BitmapDisplayer displayer
圖片的顯示方式,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為SimpleBitmapDisplayer。
(18). Handler handler
handler 對(duì)象,默認(rèn)為 null。
(19). boolean isSyncLoading
是否同步加載,通過(guò) Builder 構(gòu)建的對(duì)象默認(rèn)為 false。
4.2.14 DisplayImageOptions.Builder.java 靜態(tài)內(nèi)部類
Builder 模式,用于構(gòu)造參數(shù)繁多的DisplayImageOptions。
其屬性與DisplayImageOptions類似,函數(shù)多是屬性設(shè)置函數(shù)。
4.2.15 ImageLoadingListener.java
圖片加載各種時(shí)刻的回調(diào)接口,可在圖片加載的某些點(diǎn)做監(jiān)聽(tīng)。
包括開(kāi)始加載(onLoadingStarted)、加載失敗(onLoadingFailed)、加載成功(onLoadingComplete)、取消加載(onLoadingCancelled)四個(gè)回調(diào)函數(shù)。
4.2.16 SimpleImageLoadingListener.java
實(shí)現(xiàn)ImageLoadingListener接口,不過(guò)各個(gè)函數(shù)都是空實(shí)現(xiàn),表示不在 Image 加載過(guò)程中做任何回調(diào)監(jiān)聽(tīng)。
ImageLoader.displayImage(…)函數(shù)中當(dāng)入?yún)istener為空時(shí)的默認(rèn)值。
4.2.17 ImageLoadingProgressListener.java
Image 加載進(jìn)度的回調(diào)接口。其中抽象函數(shù)
void onProgressUpdate(String imageUri, View view, int current, int total)
會(huì)在獲取圖片存儲(chǔ)到文件系統(tǒng)時(shí)被回調(diào)。其中total表示圖片總大小,為網(wǎng)絡(luò)請(qǐng)求結(jié)果Response Header中content-length字段,如果不存在則為 -1。
4.2.18 DisplayBitmapTask.java
顯示圖片的Task,實(shí)現(xiàn)了Runnable接口,必須在主線程調(diào)用。
主要函數(shù):
(1) run()
首先判斷imageAware是否被 GC 回收,如果是直接調(diào)用取消加載回調(diào)接口ImageLoadingListener.onLoadingCancelled(…);
否則判斷imageAware是否被復(fù)用,如果是直接調(diào)用取消加載回調(diào)接口ImageLoadingListener.onLoadingCancelled(…);
否則調(diào)用displayer顯示圖片,并將imageAware從正在加載的 map 中移除。調(diào)用加載成功回調(diào)接口ImageLoadingListener.onLoadingComplete(…)。
對(duì)于 ListView 或是 GridView 這類會(huì)緩存 Item 的 View 來(lái)說(shuō),單個(gè) Item 中如果含有 ImageView,在滑動(dòng)過(guò)程中可能因?yàn)楫惒郊虞d及 View 復(fù)用導(dǎo)致圖片錯(cuò)亂,這里對(duì)imageAware是否被復(fù)用的判斷就能很好的解決這個(gè)問(wèn)題。
原因類似:Android ListView 滑動(dòng)過(guò)程中圖片顯示重復(fù)錯(cuò)位閃爍問(wèn)題原因及解決方案。
4.2.19 ProcessAndDisplayImageTask.java
處理并顯示圖片的Task,實(shí)現(xiàn)了Runnable接口。
主要函數(shù):
(1) run()
主要通過(guò) imageLoadingInfo 得到BitmapProcessor處理圖片,并用處理后的圖片和配置新建一個(gè)DisplayBitmapTask在ImageAware中顯示圖片。
4.2.20 LoadAndDisplayImageTask.java
加載并顯示圖片的Task,實(shí)現(xiàn)了Runnable接口,用于從網(wǎng)絡(luò)、文件系統(tǒng)或內(nèi)存獲取圖片并解析,然后調(diào)用DisplayBitmapTask在ImageAware中顯示圖片。
主要函數(shù):
(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);
從上面代碼段中可以看到先是從內(nèi)存緩存中去讀取 bitmap 對(duì)象,若 bitmap 對(duì)象不存在,則調(diào)用 tryLoadBitmap() 函數(shù)獲取 bitmap 對(duì)象,獲取成功后若在 DisplayImageOptions.Builder 中設(shè)置了 cacheInMemory(true), 同時(shí)將 bitmap 對(duì)象緩存到內(nèi)存中。
最后新建DisplayBitmapTask顯示圖片。
函數(shù)流程圖如下:

1.判斷圖片的內(nèi)存緩存是否存在,若存在直接執(zhí)行步驟 8;
2.判斷圖片的磁盤緩存是否存在,若存在直接執(zhí)行步驟 5;
3.從網(wǎng)絡(luò)上下載圖片;
4.將圖片緩存在磁盤上;
5.將圖片 decode 成 bitmap 對(duì)象;
6.根據(jù)DisplayImageOptions配置對(duì)圖片進(jìn)行預(yù)處理(Pre-process Bitmap);
7.將 bitmap 對(duì)象緩存到內(nèi)存中;
8.根據(jù)DisplayImageOptions配置對(duì)圖片進(jìn)行后處理(Post-process Bitmap);
9.執(zhí)行DisplayBitmapTask將圖片顯示在相應(yīng)的控件上。流程圖可以參見(jiàn)3. 流程圖。
(2) tryLoadBitmap()
從磁盤緩存或網(wǎng)絡(luò)獲取圖片,核心代碼如下:
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);
...
}
首先根據(jù) uri 看看磁盤中是不是已經(jīng)緩存了這個(gè)文件,如果已經(jīng)緩存,調(diào)用 decodeImage 函數(shù),將圖片文件 decode 成 bitmap 對(duì)象; 如果 bitmap 不合法或緩存文件不存在,判斷是否需要緩存在磁盤,需要?jiǎng)t調(diào)用tryCacheImageOnDisk()函數(shù)去下載并緩存圖片到本地磁盤,再通過(guò)decodeImage(imageUri)函數(shù)將圖片文件 decode 成 bitmap 對(duì)象,否則直接通過(guò)decodeImage(imageUriForDecoding)下載圖片并解析。
(3) tryCacheImageOnDisk()
下載圖片并存儲(chǔ)在磁盤內(nèi),根據(jù)磁盤緩存圖片最長(zhǎng)寬高的配置處理圖片。
loaded = downloadImage();
主要就是這一句話,調(diào)用下載器下載并保存圖片。
如果你在ImageLoaderConfiguration中還配置了maxImageWidthForDiskCache或者maxImageHeightForDiskCache,還會(huì)調(diào)用resizeAndSaveImage()函數(shù),調(diào)整圖片尺寸,并保存新的圖片文件。
(4) downloadImage()
下載圖片并存儲(chǔ)在磁盤內(nèi)。調(diào)用getDownloader()得到ImageDownloader去下載圖片。
(4) resizeAndSaveImage(int maxWidth, int maxHeight)
從磁盤緩存中得到圖片,重新設(shè)置大小及進(jìn)行一些處理后保存。
(5) getDownloader()
根據(jù)ImageLoaderEngine配置得到下載器。
如果不允許訪問(wèn)網(wǎng)絡(luò),則使用不允許訪問(wèn)網(wǎng)絡(luò)的圖片下載器NetworkDeniedImageDownloader;如果是慢網(wǎng)絡(luò)情況,則使用慢網(wǎng)絡(luò)情況下的圖片下載器SlowNetworkImageDownloader;否則直接使用ImageLoaderConfiguration中的downloader。
4.2.21 ImageLoadingInfo.java
加載和顯示圖片任務(wù)需要的信息。
String uri 圖片 url。
String memoryCacheKey 圖片緩存 key。
ImageAware imageAware 需要加載圖片的對(duì)象。
ImageSize targetSize 圖片的顯示尺寸。
DisplayImageOptions options 圖片顯示的配置項(xiàng)。
ImageLoadingListener listener 圖片加載各種時(shí)刻的回調(diào)接口。
ImageLoadingProgressListener progressListener 圖片加載進(jìn)度的回調(diào)接口。
ReentrantLock loadFromUriLock 圖片加載中的重入鎖。
4.2.22 ImageDownloader.java
圖片下載接口。待實(shí)現(xiàn)函數(shù)
getStream(String imageUri, Object extra)
表示通過(guò) uri 得到 InputStream。
通過(guò)內(nèi)部定義的枚舉Scheme, 可以看出 UIL 支持哪些圖片來(lái)源。
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
4.2.23 BaseImageDownloader.java
ImageDownloader的具體實(shí)現(xiàn)類。得到上面各種Scheme對(duì)應(yīng)的圖片 InputStream。
主要函數(shù)
(1). getStream(String imageUri, Object extra)
在getStream(…)函數(shù)內(nèi)根據(jù)不同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);
}
}
具體見(jiàn)下面各函數(shù)介紹。
(2). getStreamFromNetwork(String imageUri, Object extra)
通過(guò)HttpURLConnection從網(wǎng)絡(luò)獲取圖片的InputStream。支持 response code 為 3xx 的重定向。這里有個(gè)小細(xì)節(jié)代碼如下:
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
在發(fā)生異常時(shí)會(huì)調(diào)用conn.getErrorStream()繼續(xù)讀取 Error Stream,這是為了利于網(wǎng)絡(luò)連接回收及復(fù)用。但有意思的是在 Froyo(2.2) 之前,HttpURLConnection 有個(gè)重大 Bug,調(diào)用 close() 函數(shù)會(huì)影響連接池,導(dǎo)致連接復(fù)用失效,不少庫(kù)通過(guò)在 2.3 之前使用 AndroidHttpClient 解決這個(gè)問(wèn)題。
(3). getStreamFromFile(String imageUri, Object extra)
從文件系統(tǒng)獲取圖片的InputStream。如果 uri 是 video 類型,則需要單獨(dú)得到 video 的縮略圖返回,否則按照一般讀取文件操作返回。
(4). getStreamFromContent(String imageUri, Object extra)
從 ContentProvider 獲取圖片的InputStream。
如果是 video 類型,則先從MediaStore得到 video 的縮略圖返回;
如果是聯(lián)系人類型,通過(guò)ContactsContract.Contacts.openContactPhotoInputStream(res, uri)讀取內(nèi)容返回。
否則通過(guò) ContentResolver.openInputStream(…) 讀取內(nèi)容返回。
(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 內(nèi)存緩存接口,需要實(shí)現(xiàn)的接口包括 get(…)、put(…)、remove(…)、clear()、keys()。
4.2.25 BaseMemoryCache.java
實(shí)現(xiàn)了MemoryCache主要函數(shù)的抽象類,以 Map> softMap 做為緩存池,利于虛擬機(jī)在內(nèi)存不足時(shí)回收緩存對(duì)象。提供抽象函數(shù):
protected abstract Reference<Bitmap> createReference(Bitmap value)
表示根據(jù) Bitmap 創(chuàng)建一個(gè) Reference 做為緩存對(duì)象。Reference 可以是 WeakReference、SoftReference 等。
4.2.26 WeakMemoryCache.java
以WeakReference<Bitmap>做為緩存 value 的內(nèi)存緩存,實(shí)現(xiàn)了BaseMemoryCache。
實(shí)現(xiàn)了BaseMemoryCache的createReference(Bitmap value)函數(shù),直接返回一個(gè)new WeakReference<Bitmap>(value)做為緩存 value。
4.2.27 LimitedMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,繼承自BaseMemoryCache的抽象類。
會(huì)在 put(…) 函數(shù)中判斷總體大小是否超出了上限,是則循環(huán)刪除緩存對(duì)象直到小于上限。刪除順序由抽象函數(shù)
protected abstract Bitmap removeNext() 決定。
抽象函數(shù)
protected abstract int getSize(Bitmap value) 表示每個(gè)元素大小。
4.2.28 LargestLimitedMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除 size 最大的元素,繼承自LimitedMemoryCache。
實(shí)現(xiàn)了LimitedMemoryCache緩存removeNext()函數(shù),總是返回當(dāng)前緩存中 size 最大的元素。
4.2.29 UsingFreqLimitedMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除使用次數(shù)最少的元素,繼承自LimitedMemoryCache。
實(shí)現(xiàn)了LimitedMemoryCache緩存removeNext()函數(shù),總是返回當(dāng)前緩存中使用次數(shù)最少的元素。
.2.30 LRULimitedMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除最近最少使用的元素,繼承自LimitedMemoryCache。
通過(guò)new LinkedHashMap<String, Bitmap>(10, 1.1f, true)作為緩存池。
LinkedHashMap 第三個(gè)參數(shù)表示是否需要根據(jù)訪問(wèn)順序(accessOrder)排序,true 表示根據(jù)accessOrder排序,最近訪問(wèn)的跟最新加入的一樣放到最后面,false 表示根據(jù)插入順序排序。這里為 true 且緩存滿時(shí)始終刪除第一個(gè)元素,即始終刪除最近最少訪問(wèn)的元素。
實(shí)現(xiàn)了LimitedMemoryCache緩存removeNext()函數(shù),總是返回第一個(gè)元素,即最近最少使用的元素。
4.2.31 FIFOLimitedMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除先進(jìn)入緩存的元素,繼承自LimitedMemoryCache。
實(shí)現(xiàn)了LimitedMemoryCache緩存removeNext()函數(shù),總是返回最先進(jìn)入緩存的元素。
以上所有LimitedMemoryCache子類都有個(gè)問(wèn)題,就是 Bitmap 雖然通過(guò)WeakReference<Bitmap>包裝,但實(shí)際根本不會(huì)被虛擬機(jī)回收,因?yàn)樗麄冏宇愔型瑫r(shí)都保留了 Bitmap 的強(qiáng)引用。大都是 UIL 早期實(shí)現(xiàn)的版本,不推薦使用。
4.2.32 LruMemoryCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除最近最少使用的元素,實(shí)現(xiàn)了MemoryCache。LRU(Least Recently Used) 為最近最少使用算法。
以new LinkedHashMap<String, Bitmap>(0, 0.75f, true)作為緩存池。
LinkedHashMap 第三個(gè)參數(shù)表示是否需要根據(jù)訪問(wèn)順序(accessOrder)排序,true 表示根據(jù)accessOrder排序,最近訪問(wèn)的跟最新加入的一樣放到最后面,false 表示根據(jù)插入順序排序。這里為 true 且緩存滿時(shí)始終刪除第一個(gè)元素,即始終刪除最近最少訪問(wèn)的元素。
在put(…)函數(shù)中通過(guò)trimToSize(int maxSize)函數(shù)判斷總體大小是否超出了上限,是則刪除第緩存池中第一個(gè)元素,即最近最少使用的元素,直到總體大小小于上限。
LruMemoryCache功能上與LRULimitedMemoryCache類似,不過(guò)在實(shí)現(xiàn)上更加優(yōu)雅。用簡(jiǎn)單的實(shí)現(xiàn)接口方式,而不是不斷繼承的方式。
4.2.33 LimitedAgeMemoryCache.java
限制了對(duì)象最長(zhǎng)存活周期的內(nèi)存緩存。
MemoryCache的裝飾者,相當(dāng)于為MemoryCache添加了一個(gè)特性。以一個(gè)MemoryCache內(nèi)存緩存和一個(gè) maxAge 做為構(gòu)造函數(shù)入?yún)ⅰT?get(…) 時(shí)判斷如果對(duì)象存活時(shí)間已經(jīng)超過(guò)設(shè)置的最長(zhǎng)時(shí)間,則刪除。
4.2.34 FuzzyKeyMemoryCache.java
可以將某些原本不同的 key 看做相等,在 put 時(shí)刪除這些相等的 key。
MemoryCache的裝飾者,相當(dāng)于為MemoryCache添加了一個(gè)特性。以一個(gè)MemoryCache內(nèi)存緩存和一個(gè) keyComparator 做為構(gòu)造函數(shù)入?yún)ⅰT?put(…) 時(shí)判斷如果 key 與緩存中已有 key 經(jīng)過(guò)Comparator比較后相等,則刪除之前的元素。
4.2.35 FileNameGenerator.java
根據(jù) uri 得到文件名的接口。
4.2.36 HashCodeFileNameGenerator.java
以 uri 的 hashCode 作為文件名。
4.2.37 Md5FileNameGenerator.java
以 uri 的 MD5 值作為文件名。
4.2.38 DiskCache.java
圖片的磁盤緩存接口。
主要函數(shù):
(1) File get(String imageUri)
根據(jù)原始圖片的 uri 去獲取緩存圖片的文件。
(2) boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)
保存 imageStream 到磁盤中,listener 表示保存進(jìn)度且可在其中取消某些段的保存。
(3) boolean save(String imageUri, Bitmap bitmap)
保存圖片到磁盤。
(4) boolean remove(String imageUri)
根據(jù)圖片 uri 刪除緩存圖片。
(5) void close()
關(guān)閉磁盤緩存,并釋放資源。
(6) void clear()
清空磁盤緩存。
(7) File getDirectory()
得到磁盤緩存的根目錄。
4.2.39 BaseDiskCache.java
一個(gè)無(wú)大小限制的本地圖片緩存,實(shí)現(xiàn)了DiskCache主要函數(shù)的抽象類。
圖片緩存在cacheDir文件夾內(nèi),當(dāng)cacheDir不可用時(shí),則使用備庫(kù)reserveCacheDir。
主要函數(shù):
(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)
先根據(jù)imageUri得到目標(biāo)文件,將imageStream先寫(xiě)入與目標(biāo)文件同一文件夾的 .tmp 結(jié)尾的臨時(shí)文件內(nèi),若未被listener取消且寫(xiě)入成功則將臨時(shí)文件重命名為目標(biāo)文件并返回 true,否則刪除臨時(shí)文件并返回 false。
(2). save(String imageUri, Bitmap bitmap)
先根據(jù)imageUri得到目標(biāo)文件,通過(guò)Bitmap.compress(…)函數(shù)將bitmap先寫(xiě)入與目標(biāo)文件同一文件夾的 .tmp 結(jié)尾的臨時(shí)文件內(nèi),若寫(xiě)入成功則將臨時(shí)文件重命名為目標(biāo)文件并返回 true,否則刪除臨時(shí)文件并返回 false。
(3). File getFile(String imageUri)
根據(jù) imageUri 和 fileNameGenerator得到文件名,返回cacheDir內(nèi)該文件,若cacheDir不可用,則使用備庫(kù)reserveCacheDir。
4.2.40 LimitedAgeDiskCache.java
限制了緩存對(duì)象最長(zhǎng)存活周期的磁盤緩存,繼承自BaseDiskCache。
在 get(…) 時(shí)判斷如果緩存對(duì)象存活時(shí)間已經(jīng)超過(guò)設(shè)置的最長(zhǎng)時(shí)間,則刪除。在 save(…) 時(shí)保存當(dāng)存時(shí)間作為對(duì)象的創(chuàng)建時(shí)間。
4.2.41 UnlimitedDiskCache.java
一個(gè)無(wú)大小限制的本地圖片緩存。與BaseDiskCache無(wú)異,只是用了個(gè)意思明確的類名。
4.2.42 DiskLruCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除最近最少使用的元素。
通過(guò)緩存目錄下名為journal的文件記錄緩存的所有操作,并在緩存open時(shí)讀取journal的文件內(nèi)容存儲(chǔ)到LinkedHashMap<String, Entry> lruEntries中,后面get(String key)獲取緩存內(nèi)容時(shí),會(huì)先從lruEntries中得到圖片文件名返回文件。
LRU 的實(shí)現(xiàn)跟上面內(nèi)存緩存類似,lruEntries為new LinkedHashMap<String, Entry>(0, 0.75f, true),LinkedHashMap 第三個(gè)參數(shù)表示是否需要根據(jù)訪問(wèn)順序(accessOrder)排序,true 表示根據(jù)accessOrder排序,最近訪問(wèn)的跟最新加入的一樣放到最后面,false 表示根據(jù)插入順序排序。這里為 true 且緩存滿時(shí)trimToSize()函數(shù)始終刪除第一個(gè)元素,即始終刪除最近最少訪問(wèn)的文件。
來(lái)源于 JakeWharton 的開(kāi)源項(xiàng)目 DiskLruCache,具體分析請(qǐng)等待 DiskLruCache 源碼解析 完成。
4.2.43 LruDiskCache.java
限制總字節(jié)大小的內(nèi)存緩存,會(huì)在緩存滿時(shí)優(yōu)先刪除最近最少使用的元素,實(shí)現(xiàn)了DiskCache。
內(nèi)部有個(gè)DiskLruCache cache屬性,緩存的存、取操作基本都是由該屬性代理完成。
4.2.44 StrictLineReader.java
通過(guò)readLine()函數(shù)從InputStream中讀取一行,目前僅用于磁盤緩存操作記錄文件journal的解析。
4.2.45 Util.java
工具類。
String readFully(Reader reader)讀取 reader 中內(nèi)容。
deleteContents(File dir)遞歸刪除文件夾內(nèi)容。
4.2.46 ContentLengthInputStream.java
InputStream的裝飾者,可通過(guò)available()函數(shù)得到 InputStream 對(duì)應(yīng)數(shù)據(jù)源的長(zhǎng)度(總字節(jié)數(shù))。主要用于計(jì)算文件存儲(chǔ)進(jìn)度即圖片下載進(jìn)度時(shí)的總進(jìn)度。
4.2.47 FailReason.java
圖片下載及顯示時(shí)的錯(cuò)誤原因,目前包括:
IO_ERROR 網(wǎng)絡(luò)連接或是磁盤存儲(chǔ)錯(cuò)誤。
DECODING_ERROR decode image 為 Bitmap 時(shí)錯(cuò)誤。
NETWORK_DENIED 當(dāng)圖片不在緩存中,且設(shè)置不允許訪問(wèn)網(wǎng)絡(luò)時(shí)的錯(cuò)誤。
OUT_OF_MEMORY 內(nèi)存溢出錯(cuò)誤。
UNKNOWN 未知錯(cuò)誤。
4.2.48 FlushedInputStream.java
為了解決早期 Android 版本BitmapFactory.decodeStream(…)在慢網(wǎng)絡(luò)情況下 decode image 異常的 Bug。
主要通過(guò)重寫(xiě)FilterInputStream的 skip(long n) 函數(shù)解決,確保 skip(long n) 始終跳過(guò)了 n 個(gè)字節(jié)。如果返回結(jié)果即跳過(guò)的字節(jié)數(shù)小于 n,則不斷循環(huán)直到 skip(long n) 跳過(guò) n 字節(jié)或到達(dá)文件尾。
4.2.49 ImageScaleType.java
Image 的縮放類型,目前包括:
NONE不縮放。
NONE_SAFE根據(jù)需要以整數(shù)倍縮小圖片,使得其尺寸不超過(guò) Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2根據(jù)需要以 2 的 n 次冪縮小圖片,使其尺寸不超過(guò)目標(biāo)大小,比較快的縮小方式。
IN_SAMPLE_INT根據(jù)需要以整數(shù)倍縮小圖片,使其尺寸不超過(guò)目標(biāo)大小。
EXACTLY根據(jù)需要縮小圖片到寬或高有一個(gè)與目標(biāo)尺寸一致。
EXACTLY_STRETCHED根據(jù)需要縮放圖片到寬或高有一個(gè)與目標(biāo)尺寸一致。
4.2.50 ViewScaleType.java
ImageAware的 ScaleType。
將 ImageView 的 ScaleType 簡(jiǎn)化為兩種FIT_INSIDE和CROP兩種。FIT_INSIDE表示將圖片縮放到至少寬度和高度有一個(gè)小于等于 View 的對(duì)應(yīng)尺寸,CROP表示將圖片縮放到寬度和高度都大于等于 View 的對(duì)應(yīng)尺寸。
4.2.51 ImageSize.java
表示圖片寬高的類。
scaleDown(…) 等比縮小寬高。
scale(…) 等比放大寬高。
4.2.52 LoadedFrom.java
圖片來(lái)源枚舉類,包括網(wǎng)絡(luò)、磁盤緩存、內(nèi)存緩存。
4.2.53 ImageDecoder.java
將圖片轉(zhuǎn)換為 Bitmap 的接口,抽象函數(shù):
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
表示根據(jù)ImageDecodingInfo信息得到圖片并根據(jù)參數(shù)將其轉(zhuǎn)換為 Bitmap。
4.2.54 BaseImageDecoder.java
實(shí)現(xiàn)了ImageDecoder。調(diào)用ImageDownloader獲取圖片,然后根據(jù)ImageDecodingInfo或圖片 Exif 信息處理圖片轉(zhuǎn)換為 Bitmap。
主要函數(shù):
(1). decode(ImageDecodingInfo decodingInfo)
調(diào)用ImageDownloader獲取圖片,再調(diào)用defineImageSizeAndRotation(…)函數(shù)得到圖片的相關(guān)信息,調(diào)用prepareDecodingOptions(…)得到圖片縮放的比例,調(diào)用BitmapFactory.decodeStream將 InputStream 轉(zhuǎn)換為 Bitmap,最后調(diào)用considerExactScaleAndOrientatiton(…)根據(jù)參數(shù)將圖片放大、翻轉(zhuǎn)、旋轉(zhuǎn)為合適的樣子返回。
(2). defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
得到圖片真實(shí)大小以及 Exif 信息(設(shè)置考慮 Exif 的條件下)。
(3). defineExifOrientation(String imageUri)
得到圖片 Exif 信息中的翻轉(zhuǎn)以及旋轉(zhuǎn)角度信息。
(4). prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo)
得到圖片縮放的比例。
如果scaleType等于ImageScaleType.NONE,則縮放比例為 1;
如果scaleType等于ImageScaleType.NONE_SAFE,則縮放比例為 (int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight));
否則,調(diào)用ImageSizeUtils.computeImageSampleSize(…)計(jì)算縮放比例。
在 computeImageSampleSize(…) 中
如果viewScaleType等于ViewScaleType.FIT_INSIDE;
1.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,則縮放比例從 1 開(kāi)始不斷 *2 直到寬或高小于最大尺寸;
1.2 否則取寬和高分別與最大尺寸比例中較大值,即Math.max(srcWidth / targetWidth, srcHeight / targetHeight)。
如果scaleType等于ViewScaleType.CROP;
2.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,則縮放比例從 1 開(kāi)始不斷 *2 直到寬和高都小于最大尺寸。
2.2 否則取寬和高分別與最大尺寸比例中較小值,即Math.min(srcWidth / targetWidth, srcHeight / targetHeight)。
最后判斷寬和高是否超過(guò)最大值,如果是 *2 或是 +1 縮放。
(5). considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal)
根據(jù)參數(shù)將圖片放大、翻轉(zhuǎn)、旋轉(zhuǎn)為合適的樣子返回。
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 對(duì)象的接口??稍趯?shí)現(xiàn)中對(duì) bitmap 做一些額外處理,比如加圓角、動(dòng)畫(huà)效果。
4.2.57 FadeInBitmapDisplayer.java
圖片淡入方式顯示在ImageAware中,實(shí)現(xiàn)了BitmapDisplayer接口。
4.2.58 RoundedBitmapDisplayer.java
為圖片添加圓角顯示在ImageAware中,實(shí)現(xiàn)了BitmapDisplayer接口。主要通過(guò)BitmapShader實(shí)現(xiàn)。
4.2.59 RoundedVignetteBitmapDisplayer.java
為圖片添加漸變效果的圓角顯示在ImageAware中,實(shí)現(xiàn)了BitmapDisplayer接口。主要通過(guò)RadialGradient實(shí)現(xiàn)。
4.2.60 SimpleBitmapDisplayer.java
直接將圖片顯示在ImageAware中,實(shí)現(xiàn)了BitmapDisplayer接口。
4.2.61 BitmapProcessor.java
圖片處理接口??捎糜趯?duì)圖片預(yù)處理(Pre-process Bitmap)和后處理(Post-process Bitmap)。抽象函數(shù):
public interface BitmapProcessor {
Bitmap process(Bitmap bitmap);
}
用戶可以根據(jù)自己需求去實(shí)現(xiàn)它。比如你想要為你的圖片添加一個(gè)水印,那么可以自己去實(shí)現(xiàn) BitmapProcessor 接口,在DisplayImageOptions中配置 Pre-process 階段預(yù)處理圖片,這樣設(shè)置后存儲(chǔ)在文件系統(tǒng)以及內(nèi)存緩存中的圖片都是加了水印后的。如果只希望在顯示時(shí)改變不動(dòng)原圖片,可以在BitmapDisplayer中處理。
4.2.62 PauseOnScrollListener.java
可在 View 滾動(dòng)過(guò)程中暫停圖片加載的 Listener,實(shí)現(xiàn)了 OnScrollListener 接口。
它的好處是防止?jié)L動(dòng)中不必要的圖片加載,比如快速滾動(dòng)不希望滾動(dòng)中的圖片加載。在 ListView 或 GridView 中 item 加載圖片最好使用它,簡(jiǎn)單的一行代碼:
gridView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));
主要的成員變量:
pauseOnScroll 觸摸滑動(dòng)(手指依然在屏幕上)過(guò)程中是否暫停圖片加載。
pauseOnFling 甩指滾動(dòng)(手指已離開(kāi)屏幕)過(guò)程中是否暫停圖片加載。
externalListener 自定義的 OnScrollListener 接口,適用于 View 原來(lái)就有自定義 OnScrollListener 情況設(shè)置。
實(shí)現(xiàn)原理:
重寫(xiě)onScrollStateChanged(…)函數(shù)判斷不同的狀態(tài)下暫停或繼續(xù)圖片加載。
OnScrollListener.SCROLL_STATE_IDLE表示 View 處于空閑狀態(tài),沒(méi)有在滾動(dòng),這時(shí)候會(huì)加載圖片。
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL表示 View 處于觸摸滑動(dòng)狀態(tài),手指依然在屏幕上,通過(guò)pauseOnScroll變量確定是否需要暫停圖片加載。這種時(shí)候大都屬于慢速滾動(dòng)瀏覽狀態(tài),所以建議繼續(xù)圖片加載。
OnScrollListener.SCROLL_STATE_FLING表示 View 處于甩指滾動(dòng)狀態(tài),手指已離開(kāi)屏幕,通過(guò)pauseOnFling變量確定是否需要暫停圖片加載。這種時(shí)候大都屬于快速滾動(dòng)狀態(tài),所以建議暫停圖片加載以節(jié)省資源。
4.2.63 QueueProcessingType.java
任務(wù)隊(duì)列的處理類型,包括FIFO先進(jìn)先出、LIFO后進(jìn)先出。
4.2.64 LIFOLinkedBlockingDeque.java
后進(jìn)先出阻塞隊(duì)列。重寫(xiě)LinkedBlockingDeque的offer(…)函數(shù)如下:
@Override
public boolean offer(T e) {
return super.offerFirst(e);
}
讓LinkedBlockingDeque插入總在最前,而remove()本身始終刪除第一個(gè)元素,所以就變?yōu)榱撕筮M(jìn)先出阻塞隊(duì)列。
實(shí)際一般情況只重寫(xiě)offer(…)函數(shù)是不夠的,但因?yàn)門hreadPoolExecutor默認(rèn)只用到了BlockingQueue的offer(…)函數(shù),所以這種簡(jiǎn)單重寫(xiě)后做為ThreadPoolExecutor的任務(wù)隊(duì)列沒(méi)問(wèn)題。
LIFOLinkedBlockingDeque.java包下的LinkedBlockingDeque.java、BlockingDeque.java、Deque.java都是 Java 1.6 源碼中的,這里不做分析。
4.2.65 DiskCacheUtils.java
磁盤緩存工具類,可用于查找或刪除某個(gè) uri 對(duì)應(yīng)的磁盤緩存。
4.2.66 MemoryCacheUtils.java
內(nèi)存緩存工具類??捎糜诟鶕?jù) uri 生成內(nèi)存緩存 key,緩存 key 比較,根據(jù) uri 得到所有相關(guān)的 key 或圖片,刪除某個(gè) uri 的內(nèi)存緩存。
generateKey(String imageUri, ImageSize targetSize)
根據(jù) uri 生成內(nèi)存緩存 key,key 規(guī)則為[imageUri]_[width]x[height]。
4.2.67 StorageUtils.java
得到圖片 SD 卡緩存目錄路徑。
緩存目錄優(yōu)先選擇/Android/data/[app_package_name]/cache;若無(wú)權(quán)限或不可用,則選擇 App 在文件系統(tǒng)的緩存目錄context.getCacheDir();若無(wú)權(quán)限或不可用,則選擇/data/data/[app_package_name]/cache。
如果緩存目錄選擇了/Android/data/[app_package_name]/cache,則新建.nomedia文件表示不允許類似 Galley 這些應(yīng)用顯示此文件夾下圖片。不過(guò)在 4.0 系統(tǒng)有 Bug 這種方式不生效。
4.2.68 ImageSizeUtils.java
用于計(jì)算圖片尺寸、縮放比例相關(guān)的工具類。
4.2.69 IoUtils.java
IO 相關(guān)工具類,包括 stream 拷貝,關(guān)閉等。
4.2.70 L.java
Log 工具類。
5. 雜談
聊聊 LRU
UIL 的內(nèi)存緩存默認(rèn)使用了 LRU 算法。 LRU: Least Recently Used 近期最少使用算法, 選用了基于鏈表結(jié)構(gòu)的 LinkedHashMap 作為存儲(chǔ)結(jié)構(gòu)。
假設(shè)情景:內(nèi)存緩存設(shè)置的閾值只夠存儲(chǔ)兩個(gè) bitmap 對(duì)象,當(dāng) put 第三個(gè) bitmap 對(duì)象時(shí),將近期最少使用的 bitmap 對(duì)象移除。
圖 1: 初始化 LinkedHashMap, 并按使用順序來(lái)排序, accessOrder = true;
圖 2: 向緩存池中放入 bitmap1 和 bitmap2 兩個(gè)對(duì)象。
圖 3: 繼續(xù)放入第三個(gè) bitmap3,根據(jù)假設(shè)情景,將會(huì)超過(guò)設(shè)定緩存池閾值。
圖 4: 釋放對(duì) bitmap1 對(duì)象的引用。
圖 5: bitmap1 對(duì)象被 GC 回收。




