Android Universal Image Loader 是一個強大的、可高度定製的圖片緩存,本文簡稱爲UIL
。
簡單的說 UIL 就作了一件事——獲取圖片並顯示在相應的控件上。java
添加完依賴後在Application
或Activity
中初始化ImageLoader
,以下:android
public class YourApplication extends Application { @Override public void onCreate() { super.onCreate(); ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this) // 添加你的配置需求 .build(); ImageLoader.getInstance().init(configuration); } }
其中 configuration 表示ImageLoader
的配置信息,可包括圖片最大尺寸、線程池、緩存、下載器、解碼器等等。git
<manifest> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:name=".YourApplication" …… > …… </application> </manifest>
添加網絡權限。若是容許磁盤緩存,須要添加寫外設的權限。github
下載圖片,解析爲 Bitmap 並在 ImageView 中顯示。算法
imageLoader.displayImage(imageUri, imageView);
下載圖片,解析爲 Bitmap 傳遞給回調接口。緩存
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() { @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { // 圖片處理 } });
以上是簡單使用,更復雜 API 見本文詳細設計
。性能優化
上面是 UIL 的整體設計圖。整個庫分爲ImageLoaderEngine
,Cache
及ImageDownloader
,ImageDecoder
,BitmapDisplayer
,BitmapProcessor
五大模塊,其中Cache
分爲MemoryCache
和DiskCache
兩部分。網絡
簡單的講就是ImageLoader
收到加載及顯示圖片的任務,並將它交給ImageLoaderEngine
,ImageLoaderEngine
分發任務到具體線程池去執行,任務經過Cache
及ImageDownloader
獲取圖片,中間可能通過BitmapProcessor
和ImageDecoder
處理,最終轉換爲Bitmap
交給BitmapDisplayer
在ImageAware
中顯示。多線程
簡單介紹一些概念,在4\. 詳細設計
中會仔細介紹。
ImageLoaderEngine:任務分發器,負責分發LoadAndDisplayImageTask
和ProcessAndDisplayImageTask
給具體的線程池去執行,本文中也稱其爲engine
,具體參考4.2.6 ImageLoaderEngine.java
。併發
ImageAware:顯示圖片的對象,能夠是ImageView
等,具體參考4.2.9 ImageAware.java
。
ImageDownloader:圖片下載器,負責從圖片的各個來源獲取輸入流, 具體參考4.2.22 ImageDownloader.java
。
Cache:圖片緩存,分爲MemoryCache
和DiskCache
兩部分。
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
。
上圖爲圖片加載及顯示流程圖,在 uil 庫中給出,這裏用中文從新畫出。
圖片加載器,對外的主要 API,採起了單例模式,用於圖片的加載和顯示。
主要函數:
獲得ImageLoader
的單例。經過雙層是否爲 null 判斷提升性能。
初始化配置參數,參數configuration
爲ImageLoader
的配置信息,包括圖片最大尺寸、任務線程池、磁盤緩存、下載器、解碼器等等。
實現中會初始化ImageLoaderEngine engine
屬性,該屬性爲任務分發器。
加載並顯示圖片或加載並執行回調接口。ImageLoader
加載圖片主要分爲三類接口:
displayImage(…)
表示異步加載並顯示圖片到對應的ImageAware
上。loadImage(…)
表示異步加載圖片並執行回調接口。loadImageSync(…)
表示同步加載圖片。以上三類接口最終都會調用到這個函數進行圖片加載。函數參數解釋以下:
uri: 圖片的 uri。uri 支持多種來源的圖片,包括 http、https、file、content、assets、drawable 及自定義,具體介紹可見ImageDownloader
。
imageAware: 一個接口,表示須要加載圖片的對象,可包裝 View。
options: 圖片顯示的配置項。好比加載前、加載中、加載失敗應該顯示的佔位圖片,圖片是否須要在磁盤緩存,是否須要在內存緩存等。
listener: 圖片加載各類時刻的回調接口,包括開始加載、加載失敗、加載成功、取消加載四個時刻的回調函數。
progressListener: 圖片加載進度的回調接口。
函數流程圖以下:
ImageLoader
的配置信息,包括圖片最大尺寸、線程池、緩存、下載器、解碼器等等。
主要屬性:
程序本地資源訪問器,用於加載DisplayImageOptions
中設置的一些 App 中圖片資源。
內存緩存的圖片最大寬度。
內存緩存的圖片最大高度。
磁盤緩存的圖片最大寬度。
磁盤緩存的圖片最大高度。
圖片處理器,用於處理從磁盤緩存中讀取到的圖片。
ImageLoaderEngine
中用於執行從源獲取圖片任務的 Executor。
ImageLoaderEngine
中用於執行從緩存獲取圖片任務的 Executor。
用戶是否自定義了上面的 taskExecutor。
用戶是否自定義了上面的 taskExecutorForCachedImages。
上面兩個默認線程池的核心池大小,即最大併發數。
上面兩個默認線程池的線程優先級。
上面兩個默認線程池的線程隊列類型。目前只有 FIFO, LIFO 兩種可供選擇。
圖片內存緩存。
圖片磁盤緩存,通常放在 SD 卡。
圖片下載器。
圖片解碼器,內部可以使用咱們經常使用的BitmapFactory.decode(…)
將圖片資源解碼成Bitmap
對象。
圖片顯示的配置項。好比加載前、加載中、加載失敗應該顯示的佔位圖片,圖片是否須要在磁盤緩存,是否須要在內存緩存等。
不容許訪問網絡的圖片下載器。
慢網絡狀況下的圖片下載器。
Builder 模式,用於構造參數繁多的ImageLoaderConfiguration
。
其屬性與ImageLoaderConfiguration
相似,函數可能是屬性設置函數。
主要函數及含義:
按照配置,生成 ImageLoaderConfiguration。代碼以下:
public ImageLoaderConfiguration build() { initEmptyFieldsWithDefaultValues(); return new ImageLoaderConfiguration(this); }
初始化值爲null
的屬性。若用戶沒有配置相關項,UIL 會經過調用DefaultConfigurationFactory
中的函數返回一個默認值當配置。taskExecutorForCachedImages
、taskExecutor
及ImageLoaderEngine
的taskDistributor
的默認值以下:
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
默認值與diskCacheSize
和diskCacheFileCount
值有關,若是他們有一個大於 0,則默認爲LruDiskCache
,不然使用無大小限制的UnlimitedDiskCache
。downloader
默認值爲BaseImageDownloader
。decoder
默認值爲BaseImageDecoder
。
詳細及其餘屬性默認值請到DefaultConfigurationFactory
中查看。
設置內存緩存不容許緩存一張圖片的多個尺寸,默認容許。
後面會講到 View 的 getWidth()
在初始化先後的不一樣值與這個設置的關係。
設置磁盤緩存的最大字節數,若是大於 0 或者下面的maxFileCount
大於 0,默認的DiskCache
會用LruDiskCache
,不然使用無大小限制的UnlimitedDiskCache
。
設置磁盤緩存文件夾下最大文件數,若是大於 0 或者上面的maxCacheSize
大於 0,默認的DiskCache
會用LruDiskCache
,不然使用無大小限制的UnlimitedDiskCache
。
不容許訪問網絡的圖片下載器,實現了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); } }
慢網絡狀況下的圖片下載器,實現了ImageDownloader
接口。
經過包裝一個ImageDownloader
對象實現,在 getStream(…) 函數中當 Scheme 爲 Http 和 Https 時,用FlushedInputStream
代替InputStream
處理慢網絡狀況,具體見後面FlushedInputStream
的介紹。
LoadAndDisplayImageTask
和ProcessAndDisplayImageTask
任務分發器,負責分發任務給具體的線程池。
主要屬性:
ImageLoader
的配置信息,可包括圖片最大尺寸、線程池、緩存、下載器、解碼器等等。
用於執行從源獲取圖片任務的 Executor,爲configuration
中的 taskExecutor,若是爲null
,則會調用DefaultConfigurationFactory.createExecutor(…)
根據配置返回一個默認的線程池。
用於執行從緩存獲取圖片任務的 Executor,爲configuration
中的 taskExecutorForCachedImages,若是爲null
,則會調用DefaultConfigurationFactory.createExecutor(…)
根據配置返回一個默認的線程池。
任務分發線程池,任務指LoadAndDisplayImageTask
和ProcessAndDisplayImageTask
,由於只須要分發給上面的兩個 Executor 去執行任務,不存在較耗時或阻塞操做,因此用無併發數(Int 最大值)限制的線程池便可。
ImageAware
與內存緩存 key 對應的 map,key 爲ImageAware
的 id,value 爲內存緩存的 key。
圖片正在加載的重入鎖 map,key 爲圖片的 uri,value 爲標識其正在加載的重入鎖。
是否被暫停。若是爲true
,則全部新的加載或顯示任務都會等待直到取消暫停(爲false
)。
是否不容許訪問網絡,若是爲true
,經過ImageLoadingListener.onLoadingFailed(…)
獲取圖片,則全部不在緩存中須要網絡訪問的請求都會失敗,返回失敗緣由爲網絡訪問被禁止
。
是不是慢網絡狀況,若是爲true
,則自動調用SlowNetworkImageDownloader
下載圖片。
暫停的等待鎖,可在engine
被暫停後調用這個鎖等待。
主要函數:
添加一個LoadAndDisplayImageTask
。直接用taskDistributor
執行一個 Runnable,在 Runnable 內部根據圖片是否被磁盤緩存過肯定使用taskExecutorForCachedImages
仍是taskExecutor
執行該 task。
添加一個ProcessAndDisplayImageTask
。直接用taskExecutorForCachedImages
執行該 task。
暫停圖片加載任務。全部新的加載或顯示任務都會等待直到取消暫停(爲false
)。
繼續圖片加載任務。
暫停全部加載和顯示圖片任務並清除這裏的內部屬性值。
taskDistributor
當即執行某個任務。
獲得某個 uri 的重入鎖,若是不存在則新建。
調用DefaultConfigurationFactory.createExecutor(…)
建立一個線程池。
獲得某個imageAware
正在加載的圖片 uri。
準備開始一個Task
。向cacheKeysForImageAwares
中插入ImageAware
的 id 和圖片在內存緩存中的 key。
取消一個顯示任務。從cacheKeysForImageAwares
中刪除ImageAware
對應元素。
設置是否不容許網絡訪問。
設置是否慢網絡狀況。
爲ImageLoaderConfiguration
及ImageLoaderEngine
提供一些默認配置。
主要函數:
建立線程池。threadPoolSize
表示核心池大小(最大併發數)。threadPriority
表示線程優先級。tasksProcessingType
表示線程隊列類型,目前只有 FIFO, LIFO 兩種可供選擇。
內部實現會調用createThreadFactory(…)
返回一個支持線程優先級設置,而且以固定規則命名新建的線程的線程工廠類DefaultConfigurationFactory.DefaultThreadFactory
。
爲ImageLoaderEngine
中的任務分發器taskDistributor
提供線程池,該線程池爲 normal 優先級的無併發大小限制的線程池。
返回一個HashCodeFileNameGenerator
對象,即以 uri HashCode 爲文件名的文件名生成器。
建立一個 Disk Cache。若是 diskCacheSize 或者 diskCacheFileCount 大於 0,返回一個LruDiskCache
,不然返回無大小限制的UnlimitedDiskCache
。
建立一個 Memory Cache。返回一個LruMemoryCache
,若 memoryCacheSize 爲 0,則設置該內存緩存的最大字節數爲 App 最大可用內存的 1/8。
這裏 App 的最大可用內存也支持系統在 Honeycomb 以後(ApiLevel >= 11) application 中android:largeHeap="true"
的設置。
建立圖片下載器,返回一個BaseImageDownloader
。
建立圖片解碼器,返回一個BaseImageDecoder
。
建立圖片顯示器,返回一個SimpleBitmapDisplayer
。
默認的線程工廠類,爲DefaultConfigurationFactory.createExecutor(…)
和DefaultConfigurationFactory.createTaskDistributor(…)
提供線程工廠。支持線程優先級設置,而且以固定規則命名新建的線程。
PS:重命名線程是個很好的習慣,它的一大做用就是方便問題排查,好比性能優化,用 TraceView 查看線程,根據名字很容易分辨各個線程。
須要顯示圖片的對象的接口,可包裝 View 表示某個須要顯示圖片的 View。
主要函數:
獲得被包裝的 View,圖片在該 View 上顯示。
獲得寬度高度,在計算圖片縮放比例時會用到。
獲得惟一標識 id。ImageLoaderEngine
中用這個 id 標識正在加載圖片的ImageAware
和圖片內存緩存 key 的對應關係,圖片請求前會將內存緩存 key 與新的內存緩存 key 進行比較,若是不相等,則以前的圖片請求會被取消。這樣當ImageAware
被複用時就不會因異步加載(前面任務未取消)而形成錯亂了。
封裝 Android View 來顯示圖片的抽象類,實現了ImageAware
接口,利用Reference
來 Warp View 防止內存泄露。
主要函數:
構造函數。view
表示須要顯示圖片的對象。checkActualViewSize
表示經過getWidth()
和getHeight()
獲取圖片寬高時返回真實的寬和高,仍是LayoutParams
的寬高,true 表示返回真實寬和高。
若是爲true
會致使一個問題,View
在尚未初始化完成時加載圖片,這時它的真實寬高爲 0,會取它LayoutParams
的寬高,而圖片緩存的 key 與這個寬高有關,因此當View
初始化完成再次須要加載該圖片時,getWidth()
和getHeight()
返回的寬高都已經變化,緩存 key 不同,從而致使緩存命中失敗會再次從網絡下載一次圖片。可經過ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()
設置不容許內存緩存緩存一張圖片的多個尺寸。
若是當前操做在主線程而且 View 沒有被回收,則調用抽象函數setImageDrawableInto(Drawable drawable, View view)
去向View
設置圖片。
若是當前操做在主線程而且 View 沒有被回收,則調用抽象函數setImageBitmapInto(Bitmap bitmap, View view)
去向View
設置圖片。
封裝 Android ImageView 來顯示圖片的ImageAware
,繼承了ViewAware
,利用Reference
來 Warp View 防止內存泄露。
若是getWidth()
函數小於等於 0,會利用反射獲取mMaxWidth
的值做爲寬。
若是getHeight()
函數小於等於 0,會利用反射獲取mMaxHeight
的值做爲高。
僅包含處理圖片相關信息卻沒有須要顯示圖片的 View 的ImageAware
,實現了ImageAware
接口。經常使用於加載圖片後調用回調接口而不是顯示的狀況。
圖片顯示的配置項。好比加載前、加載中、加載失敗應該顯示的佔位圖片,圖片是否須要在磁盤緩存,是否須要在 memory 緩存等。
主要屬性及含義:
圖片正在加載中的佔位圖片的 resource id,優先級比下面的imageOnLoading
高,當存在時,imageOnLoading
不起做用。
空 uri 時的佔位圖片的 resource id,優先級比下面的imageForEmptyUri
高,當存在時,imageForEmptyUri
不起做用。
加載失敗時的佔位圖片的 resource id,優先級比下面的imageOnFail
高,當存在時,imageOnFail
不起做用。
加載中的佔位圖片的 drawabled 對象,默認爲 null。
空 uri 時的佔位圖片的 drawabled 對象,默認爲 null。
加載失敗時的佔位圖片的 drawabled 對象,默認爲 null。
在加載前是否重置 view,經過 Builder 構建的對象默認爲 false。
是否緩存在內存中,經過 Builder 構建的對象默認爲 false。
是否緩存在磁盤中,經過 Builder 構建的對象默認爲 false。
圖片的縮放類型,經過 Builder 構建的對象默認爲IN_SAMPLE_POWER_OF_2
。
爲 BitmapFactory.Options,用於BitmapFactory.decodeStream(imageStream, null, decodingOptions)
獲得圖片尺寸等信息。
設置在開始加載前的延遲時間,單位爲毫秒,經過 Builder 構建的對象默認爲 0。
是否考慮圖片的 EXIF 信息,經過 Builder 構建的對象默認爲 false。
下載器須要的輔助信息。下載時傳入ImageDownloader.getStream(String, Object)
的對象,方便用戶本身擴展,默認爲 null。
緩存在內存以前的處理程序,默認爲 null。
緩存在內存以後的處理程序,默認爲 null。
圖片的顯示方式,經過 Builder 構建的對象默認爲SimpleBitmapDisplayer
。
handler 對象,默認爲 null。
是否同步加載,經過 Builder 構建的對象默認爲 false。
Builder 模式,用於構造參數繁多的DisplayImageOptions
。
其屬性與DisplayImageOptions
相似,函數可能是屬性設置函數。
圖片加載各類時刻的回調接口,可在圖片加載的某些點作監聽。
包括開始加載(onLoadingStarted)、加載失敗(onLoadingFailed)、加載成功(onLoadingComplete)、取消加載(onLoadingCancelled)四個回調函數。
實現ImageLoadingListener
接口,不過各個函數都是空實現,表示不在 Image 加載過程當中作任何回調監聽。ImageLoader.displayImage(…)
函數中當入參listener
爲空時的默認值。
Image 加載進度的回調接口。其中抽象函數
void onProgressUpdate(String imageUri, View view, int current, int total)
會在獲取圖片存儲到文件系統時被回調。其中total
表示圖片總大小,爲網絡請求結果Response Header
中content-length
字段,若是不存在則爲 -1。
顯示圖片的Task
,實現了Runnable
接口,必須在主線程調用。
主要函數:
首先判斷imageAware
是否被 GC 回收,若是是直接調用取消加載回調接口ImageLoadingListener.onLoadingCancelled(…)
;
不然判斷imageAware
是否被複用,若是是直接調用取消加載回調接口ImageLoadingListener.onLoadingCancelled(…)
;
不然調用displayer
顯示圖片,並將imageAware
從正在加載的 map 中移除。調用加載成功回調接口ImageLoadingListener.onLoadingComplete(…)
。
對於 ListView 或是 GridView 這類會緩存 Item 的 View 來講,單個 Item 中若是含有 ImageView,在滑動過程當中可能由於異步加載及 View 複用致使圖片錯亂,這裏對imageAware
是否被複用的判斷就能很好的解決這個問題。緣由相似:Android ListView 滑動過程當中圖片顯示重複錯位閃爍問題緣由及解決方案。
處理並顯示圖片的Task
,實現了Runnable
接口。
主要函數:
主要經過 imageLoadingInfo 獲得BitmapProcessor
處理圖片,並用處理後的圖片和配置新建一個DisplayBitmapTask
在ImageAware
中顯示圖片。
加載並顯示圖片的Task
,實現了Runnable
接口,用於從網絡、文件系統或內存獲取圖片並解析,而後調用DisplayBitmapTask
在ImageAware
中顯示圖片。
主要函數:
獲取圖片並顯示,核心代碼以下:
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
顯示圖片。
函數流程圖以下:
DisplayImageOptions
配置對圖片進行預處理(Pre-process Bitmap);DisplayImageOptions
配置對圖片進行後處理(Post-process Bitmap);執行DisplayBitmapTask
將圖片顯示在相應的控件上。
流程圖能夠參見`3\. 流程圖`。
從磁盤緩存或網絡獲取圖片,核心代碼以下:
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)
下載圖片並解析。
下載圖片並存儲在磁盤內,根據磁盤緩存圖片最長寬高的配置處理圖片。
loaded = downloadImage();
主要就是這一句話,調用下載器下載並保存圖片。
若是你在ImageLoaderConfiguration
中還配置了maxImageWidthForDiskCache
或者maxImageHeightForDiskCache
,還會調用resizeAndSaveImage()
函數,調整圖片尺寸,並保存新的圖片文件。
下載圖片並存儲在磁盤內。調用getDownloader()
獲得ImageDownloader
去下載圖片。
從磁盤緩存中獲得圖片,從新設置大小及進行一些處理後保存。
根據ImageLoaderEngine
配置獲得下載器。
若是不容許訪問網絡,則使用不容許訪問網絡的圖片下載器NetworkDeniedImageDownloader
;若是是慢網絡狀況,則使用慢網絡狀況下的圖片下載器SlowNetworkImageDownloader
;不然直接使用ImageLoaderConfiguration
中的downloader
。
加載和顯示圖片任務須要的信息。String uri
圖片 url。String memoryCacheKey
圖片緩存 key。ImageAware imageAware
須要加載圖片的對象。ImageSize targetSize
圖片的顯示尺寸。DisplayImageOptions options
圖片顯示的配置項。ImageLoadingListener listener
圖片加載各類時刻的回調接口。ImageLoadingProgressListener progressListener
圖片加載進度的回調接口。ReentrantLock loadFromUriLock
圖片加載中的重入鎖。
圖片下載接口。待實現函數
getStream(String imageUri, Object extra)
表示經過 uri 獲得 InputStream。
經過內部定義的枚舉Scheme
, 能夠看出 UIL 支持哪些圖片來源。
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
ImageDownloader
的具體實現類。獲得上面各類Scheme
對應的圖片 InputStream。
主要函數
在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); } }
具體見下面各函數介紹。
經過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 解決這個問題。
從文件系統獲取圖片的InputStream
。若是 uri 是 video 類型,則須要單獨獲得 video 的縮略圖返回,不然按照通常讀取文件操做返回。
從 ContentProvider 獲取圖片的InputStream
。
若是是 video 類型,則先從MediaStore
獲得 video 的縮略圖返回;
若是是聯繫人類型,經過ContactsContract.Contacts.openContactPhotoInputStream(res, uri)
讀取內容返回。
不然經過 ContentResolver.openInputStream(…) 讀取內容返回。
從 Assets 中獲取圖片的InputStream
。
從 Drawable 資源中獲取圖片的InputStream
。
UNKNOWN(自定義)類型的處理,目前是直接拋出不支持的異常。
Bitmap 內存緩存接口,須要實現的接口包括 get(…)、put(…)、remove(…)、clear()、keys()。
實現了MemoryCache
主要函數的抽象類,以 Map<string, reference<bitmap="">> softMap 作爲緩存池,利於虛擬機在內存不足時回收緩存對象。提供抽象函數:</string,>
protected abstract Reference<Bitmap> createReference(Bitmap value)
表示根據 Bitmap 建立一個 Reference <bitmap>作爲緩存對象。Reference 能夠是 WeakReference、SoftReference 等。</bitmap>
以WeakReference<Bitmap>
作爲緩存 value 的內存緩存,實現了BaseMemoryCache
。
實現了BaseMemoryCache
的createReference(Bitmap value)
函數,直接返回一個new WeakReference<Bitmap>(value)
作爲緩存 value。
限制總字節大小的內存緩存,繼承自BaseMemoryCache
的抽象類。
會在 put(…) 函數中判斷整體大小是否超出了上限,是則循環刪除緩存對象直到小於上限。刪除順序由抽象函數
protected abstract Bitmap removeNext()
決定。抽象函數
protected abstract int getSize(Bitmap value)
表示每一個元素大小。
限制總字節大小的內存緩存,會在緩存滿時優先刪除 size 最大的元素,繼承自LimitedMemoryCache
。
實現了LimitedMemoryCache
緩存removeNext()
函數,老是返回當前緩存中 size 最大的元素。
限制總字節大小的內存緩存,會在緩存滿時優先刪除使用次數最少的元素,繼承自LimitedMemoryCache
。
實現了LimitedMemoryCache
緩存removeNext()
函數,老是返回當前緩存中使用次數最少的元素。
限制總字節大小的內存緩存,會在緩存滿時優先刪除最近最少使用的元素,繼承自LimitedMemoryCache
。
經過new LinkedHashMap<String, Bitmap>(10, 1.1f, true)
做爲緩存池。LinkedHashMap 第三個參數表示是否須要根據訪問順序(accessOrder)排序,true 表示根據accessOrder
排序,最近訪問的跟最新加入的同樣放到最後面,false 表示根據插入順序排序。這裏爲 true 且緩存滿時始終刪除第一個元素,即始終刪除最近最少訪問的元素。
實現了LimitedMemoryCache
緩存removeNext()
函數,老是返回第一個元素,即最近最少使用的元素。
限制總字節大小的內存緩存,會在緩存滿時優先刪除先進入緩存的元素,繼承自LimitedMemoryCache
。
實現了LimitedMemoryCache
緩存removeNext()
函數,老是返回最早進入緩存的元素。
以上全部LimitedMemoryCache
子類都有個問題,就是 Bitmap 雖然經過WeakReference<Bitmap>
包裝,但實際根本不會被虛擬機回收,由於他們子類中同時都保留了 Bitmap 的強引用。大都是 UIL 早期實現的版本,不推薦使用。
限制總字節大小的內存緩存,會在緩存滿時優先刪除最近最少使用的元素,實現了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
相似,不過在實現上更加優雅。用簡單的實現接口方式,而不是不斷繼承的方式。
限制了對象最長存活週期的內存緩存。MemoryCache
的裝飾者,至關於爲MemoryCache
添加了一個特性。以一個MemoryCache
內存緩存和一個 maxAge 作爲構造函數入參。在 get(…) 時判斷若是對象存活時間已經超過設置的最長時間,則刪除。
能夠將某些本來不一樣的 key 看作相等
,在 put 時刪除這些相等
的 key。MemoryCache
的裝飾者,至關於爲MemoryCache
添加了一個特性。以一個MemoryCache
內存緩存和一個 keyComparator 作爲構造函數入參。在 put(…) 時判斷若是 key 與緩存中已有 key 通過Comparator
比較後相等,則刪除以前的元素。
根據 uri 獲得文件名的接口。
以 uri 的 hashCode 做爲文件名。
以 uri 的 MD5 值做爲文件名。
圖片的磁盤緩存接口。
主要函數:
根據原始圖片的 uri 去獲取緩存圖片的文件。
保存 imageStream 到磁盤中,listener 表示保存進度且可在其中取消某些段的保存。
保存圖片到磁盤。
根據圖片 uri 刪除緩存圖片。
關閉磁盤緩存,並釋放資源。
清空磁盤緩存。
獲得磁盤緩存的根目錄。
一個無大小限制的本地圖片緩存,實現了DiskCache
主要函數的抽象類。
圖片緩存在cacheDir
文件夾內,當cacheDir
不可用時,則使用備庫reserveCacheDir
。
主要函數:
先根據imageUri
獲得目標文件,將imageStream
先寫入與目標文件同一文件夾的 .tmp 結尾的臨時文件內,若未被listener
取消且寫入成功則將臨時文件重命名爲目標文件並返回 true,不然刪除臨時文件並返回 false。
先根據imageUri
獲得目標文件,經過Bitmap.compress(…)
函數將bitmap
先寫入與目標文件同一文件夾的 .tmp 結尾的臨時文件內,若寫入成功則將臨時文件重命名爲目標文件並返回 true,不然刪除臨時文件並返回 false。
根據 imageUri 和 fileNameGenerator
獲得文件名,返回cacheDir
內該文件,若cacheDir
不可用,則使用備庫reserveCacheDir
。
限制了緩存對象最長存活週期的磁盤緩存,繼承自BaseDiskCache
。
在 get(…) 時判斷若是緩存對象存活時間已經超過設置的最長時間,則刪除。在 save(…) 時保存當存時間做爲對象的建立時間。
一個無大小限制的本地圖片緩存。與BaseDiskCache
無異,只是用了個意思明確的類名。
限制總字節大小的內存緩存,會在緩存滿時優先刪除最近最少使用的元素。
經過緩存目錄下名爲journal
的文件記錄緩存的全部操做,並在緩存open
時讀取journal
的文件內容存儲到LinkedHashMap<String, Entry> lruEntries
中,後面get(String key)
獲取緩存內容時,會先從lruEntries
中獲得圖片文件名返回文件。
LRU 的實現跟上面內存緩存相似,lruEntries
爲new LinkedHashMap<String, Entry>(0, 0.75f, true)
,LinkedHashMap 第三個參數表示是否須要根據訪問順序(accessOrder)排序,true 表示根據accessOrder
排序,最近訪問的跟最新加入的同樣放到最後面,false 表示根據插入順序排序。這裏爲 true 且緩存滿時trimToSize()
函數始終刪除第一個元素,即始終刪除最近最少訪問的文件。
來源於 JakeWharton 的開源項目 DiskLruCache,具體分析請等待 DiskLruCache 源碼解析 完成。
限制總字節大小的內存緩存,會在緩存滿時優先刪除最近最少使用的元素,實現了DiskCache
。
內部有個DiskLruCache cache
屬性,緩存的存、取操做基本都是由該屬性代理完成。
經過readLine()
函數從InputStream
中讀取一行,目前僅用於磁盤緩存操做記錄文件journal
的解析。
工具類。String readFully(Reader reader)
讀取 reader 中內容。deleteContents(File dir)
遞歸刪除文件夾內容。
InputStream
的裝飾者,可經過available()
函數獲得 InputStream 對應數據源的長度(總字節數)。主要用於計算文件存儲進度即圖片下載進度時的總進度。
圖片下載及顯示時的錯誤緣由,目前包括:IO_ERROR
網絡鏈接或是磁盤存儲錯誤。DECODING_ERROR
decode image 爲 Bitmap 時錯誤。NETWORK_DENIED
當圖片不在緩存中,且設置不容許訪問網絡時的錯誤。OUT_OF_MEMORY
內存溢出錯誤。UNKNOWN
未知錯誤。
爲了解決早期 Android 版本BitmapFactory.decodeStream(…)
在慢網絡狀況下 decode image 異常的 Bug。
主要經過重寫FilterInputStream
的 skip(long n) 函數解決,確保 skip(long n) 始終跳過了 n 個字節。若是返回結果即跳過的字節數小於 n,則不斷循環直到 skip(long n) 跳過 n 字節或到達文件尾。
Image 的縮放類型,目前包括:NONE
不縮放。NONE_SAFE
根據須要以整數倍縮小圖片,使得其尺寸不超過 Texture 可接受最大尺寸。IN_SAMPLE_POWER_OF_2
根據須要以 2 的 n 次冪縮小圖片,使其尺寸不超過目標大小,比較快的縮小方式。IN_SAMPLE_INT
根據須要以整數倍縮小圖片,使其尺寸不超過目標大小。EXACTLY
根據須要縮小圖片到寬或高有一個與目標尺寸一致。EXACTLY_STRETCHED
根據須要縮放圖片到寬或高有一個與目標尺寸一致。
ImageAware
的 ScaleType。
將 ImageView 的 ScaleType 簡化爲兩種FIT_INSIDE
和CROP
兩種。FIT_INSIDE
表示將圖片縮放到至少寬度和高度有一個小於等於 View 的對應尺寸,CROP
表示將圖片縮放到寬度和高度都大於等於 View 的對應尺寸。
表示圖片寬高的類。scaleDown(…)
等比縮小寬高。scale(…)
等比放大寬高。
圖片來源枚舉類,包括網絡、磁盤緩存、內存緩存。
將圖片轉換爲 Bitmap 的接口,抽象函數:
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
表示根據ImageDecodingInfo
信息獲得圖片並根據參數將其轉換爲 Bitmap。
實現了ImageDecoder
。調用ImageDownloader
獲取圖片,而後根據ImageDecodingInfo
或圖片 Exif 信息處理圖片轉換爲 Bitmap。
主要函數:
調用ImageDownloader
獲取圖片,再調用defineImageSizeAndRotation(…)
函數獲得圖片的相關信息,調用prepareDecodingOptions(…)
獲得圖片縮放的比例,調用BitmapFactory.decodeStream
將 InputStream 轉換爲 Bitmap,最後調用considerExactScaleAndOrientatiton(…)
根據參數將圖片放大、翻轉、旋轉爲合適的樣子返回。
獲得圖片真實大小以及 Exif 信息(設置考慮 Exif 的條件下)。
獲得圖片 Exif 信息中的翻轉以及旋轉角度信息。
獲得圖片縮放的比例。
scaleType
等於ImageScaleType.NONE
,則縮放比例爲 1;scaleType
等於ImageScaleType.NONE_SAFE
,則縮放比例爲 (int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight))
;不然,調用ImageSizeUtils.computeImageSampleSize(…)
計算縮放比例。
在 computeImageSampleSize(…) 中
若是viewScaleType
等於ViewScaleType.FIT_INSIDE
;
1.1 若是`scaleType`等於`ImageScaleType.IN_SAMPLE_POWER_OF_2`,則縮放比例從 1 開始不斷 *2 直到寬或高小於最大尺寸; 1.2 不然取寬和高分別與最大尺寸比例中較大值,即`Math.max(srcWidth / targetWidth, srcHeight / targetHeight)`。
若是scaleType
等於ViewScaleType.CROP
;
2.1 若是`scaleType`等於`ImageScaleType.IN_SAMPLE_POWER_OF_2`,則縮放比例從 1 開始不斷 *2 直到寬和高都小於最大尺寸。 2.2 不然取寬和高分別與最大尺寸比例中較小值,即`Math.min(srcWidth / targetWidth, srcHeight / targetHeight)`。
根據參數將圖片放大、翻轉、旋轉爲合適的樣子返回。
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。
在ImageAware
中顯示 bitmap 對象的接口。可在實現中對 bitmap 作一些額外處理,好比加圓角、動畫效果。
圖片淡入方式顯示在ImageAware
中,實現了BitmapDisplayer
接口。
爲圖片添加圓角顯示在ImageAware
中,實現了BitmapDisplayer
接口。主要經過BitmapShader
實現。
爲圖片添加漸變效果的圓角顯示在ImageAware
中,實現了BitmapDisplayer
接口。主要經過RadialGradient
實現。
直接將圖片顯示在ImageAware
中,實現了BitmapDisplayer
接口。
圖片處理接口。可用於對圖片預處理(Pre-process Bitmap)和後處理(Post-process Bitmap)。抽象函數:
public interface BitmapProcessor { Bitmap process(Bitmap bitmap); }
用戶能夠根據本身需求去實現它。好比你想要爲你的圖片添加一個水印,那麼能夠本身去實現 BitmapProcessor 接口,在DisplayImageOptions
中配置 Pre-process 階段預處理圖片,這樣設置後存儲在文件系統以及內存緩存中的圖片都是加了水印後的。若是隻但願在顯示時改變不動原圖片,能夠在BitmapDisplayer
中處理。
可在 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
變量肯定是否須要暫停圖片加載。這種時候大都屬於快速滾動狀態,因此建議暫停圖片加載以節省資源。
任務隊列的處理類型,包括FIFO
先進先出、LIFO
後進先出。
後進先出阻塞隊列。重寫LinkedBlockingDeque
的offer(…)
函數以下:
@Override public boolean offer(T e) { return super.offerFirst(e); }
讓LinkedBlockingDeque
插入總在最前,而remove()
自己始終刪除第一個元素,因此就變爲了後進先出阻塞隊列。
實際通常狀況只重寫offer(…)
函數是不夠的,但由於ThreadPoolExecutor
默認只用到了BlockingQueue
的offer(…)
函數,因此這種簡單重寫後作爲ThreadPoolExecutor
的任務隊列沒問題。
LIFOLinkedBlockingDeque.java
包下的LinkedBlockingDeque.java
、BlockingDeque.java
、Deque.java
都是 Java 1.6 源碼中的,這裏不作分析。
磁盤緩存工具類,可用於查找或刪除某個 uri 對應的磁盤緩存。
內存緩存工具類。可用於根據 uri 生成內存緩存 key,緩存 key 比較,根據 uri 獲得全部相關的 key 或圖片,刪除某個 uri 的內存緩存。generateKey(String imageUri, ImageSize targetSize)
根據 uri 生成內存緩存 key,key 規則爲[imageUri]_[width]x[height]
。
獲得圖片 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 這種方式不生效。
用於計算圖片尺寸、縮放比例相關的工具類。
IO 相關工具類,包括 stream 拷貝,關閉等。
Log 工具類。
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 回收。