Universal-Image-Loader的圖片載入流程原始碼分析
概述
Universal-Image-Loader是經典的圖片載入框架,雖然現在該專案不再維護,但對於初學者依舊是值得學習的開源專案之一,本文就該框架的載入圖片流程做簡要梳理,希望讀者有所收穫。
該文參考了【codeKK】 Android Universal Image Loader 原始碼分析一文,該文詳細分析了Universal-Image-Loader的設計思想,想深入瞭解,可以祥讀此文。
基本工作流程
首先來看看作者給出的工作流程圖:
上圖基本的載入流程是當圖片請求發出時,首先會從記憶體快取中查詢該bitmap是否存在,如果存在,則由BitmapProcessor
這個類來進行處理,然後由DisplayDisplaer
如果記憶體快取不存在該圖片,那麼會從硬碟快取查詢,如果存在,則由
ImageDecoder
類來將圖片解析為Bitmap
,然後由BitmapProcessor
來進行圖片處理,然後由MemoryCache
進行記憶體快取,方便下次查詢,之後再交由BitmapProcessor
和DisplayDisplaer
來進行處理和最終顯示; 如果記憶體快取和硬碟快取都沒有找到該圖片,那麼將由
ImageDownloader
來下載圖片,然後將該圖片由DiskCache
來進行硬碟快取,快取好後,之後的流程就和上面的流程一致了。
使用方法
瞭解了這個基本流程,我們看看Universal-Image-Loader
首先使用時候需要初始化設定:
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
ImageLoader.getInstance().init(configuration);
傳遞的`this`引數為`Context`型別,因此一般會在自定義的`Application`中的`onCreate`方法中進行初始化:
public class BaseApplication extends Application{
private static Context mAppContext; @Override public void onCreate() { super.onCreate(); //用於初始化預設配置,會配置一些預設引數 ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this); //將上面初始化的配置傳遞給ImageLoader ImageLoader.getInstance().init(configuration); }
}
在正式載入圖片之前,我們還可能需要做一些配置:
public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.bj_weixianshi)//載入中的等待圖片
.displayer(new SimpleBitmapDisplayer())//選擇的顯示類
.showImageOnFail(R.drawable.bj_weixianshi)//顯示失敗載入的圖片
.cacheInMemory(true)//開啟記憶體快取
.cacheOnDisk(true)//開啟硬碟快取
.bitmapConfig(Bitmap.Config.RGB_565)//圖片顯示模式
.build();
好了,這樣就可以呼叫真正的載入方法進行展示了:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
方法很簡單,第一個引數就是圖片URI,支援型別:
"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
可以看到支援本地和網路的圖片uri;
第二個引數就是ImageView
,第三個引數就是上面的simpleOptions
,通過這三個引數,就能完成圖片的顯示。
流程分析
首先我們來這個這行程式碼:
ImageLoader.getInstance().init(configuration);
看起來是個單例模式,我們跟蹤原始碼看看:
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
很典型的單例模式,保證全域性只有一個ImageLoader,不必重複建立物件,節省記憶體。
再來看看配置:
public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.bj_weixianshi)//載入中的等待圖片
...省略
.build();
因為可選引數較多,這裡用了建造者模式。
好了,看完上面,分析下載入圖片的流程:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
該方法最終會呼叫到這個方法:
public void displayImage(String uri, //圖片的uri,可能來自本地或者網路
ImageAware imageAware,//該介面型別主要封裝了一些獲取View控制元件寬高等的常見方法,實現類之一ImageViewAware
DisplayImageOptions options,//展示圖片的一些可選項封裝類,如是否快取,同步非同步等
ImageSize targetSize,//圖片顯示的最終尺寸,封裝處理類,主要是給圖片一個合理的顯示尺寸
ImageLoadingListener listener,//圖片載入監聽,載入開始,載入失敗,載入完成,載入取消幾種狀態
ImageLoadingProgressListener progressListener//圖片載入中的監聽,可用於顯示載入進度條
) {
checkConfiguration();//檢查配置是否為null,為Null報異常
if (imageAware == null) {//檢查配置是否為null,為Null報異常
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {//檢查載入監聽,如果為Null,就載入defaultListener
listener = defaultListener;
}
if (options == null) {//載入配置選項,為null,則載入預設的options
options = configuration.defaultDisplayImageOptions;
}
if (TextUtils.isEmpty(uri)) {//檢查圖片uri是否為空
engine.cancelDisplayTaskFor(imageAware);//空的情況下,取消載入任務
listener.onLoadingStarted(uri, imageAware.getWrappedView());//呼叫監聽方法
if (options.shouldShowImageForEmptyUri()) {//如果配置了空uri情況下的圖片顯示,那麼載入預設圖片
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {//否則顯示Null
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//載入完成,呼叫監聽
return;
}
if (targetSize == null) {//檢查圖片大小配置,如果為Null,生成預設的圖片大小
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//根據uri和targetSize來生成記憶體快取的Key
//將要顯示的View和key加入一個執行緒同步的快取Map,key為view的id,value為快取memoryCacheKey,該map用於判斷載入任務是否重複
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());//呼叫監聽方法
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根據Key從記憶體快取查詢是否有快取的bitmap
if (bmp != null && !bmp.isRecycled()) {//檢查快取的bitmap是否為null,或是否被回收
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
if (options.shouldPostProcess()) {//是否進行bitmap處理
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));//封裝圖片載入資訊
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));//封裝顯示和處理任務
if (options.isSyncLoading()) {//如果同步載入,displayTask將進行相關處理,並最終顯示圖片
displayTask.run();
} else {//非同步載入,提交displayTask到任務佇列,再進行處理顯示
engine.submit(displayTask);
}
} else {//不進行bitmap處理,直接顯示圖片
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);//載入完成,呼叫監聽
}
} else {//如果記憶體中沒有快取的bitmap,則根據uri從本地快取或網路載入
if (options.shouldShowImageOnLoading()) {//如果設定了載入中的圖片,則進行顯示
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {//如果設定了載入前重置圖片,那麼給圖片設定Null
imageAware.setImageDrawable(null);
}
//封裝載入資訊
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));//封裝載入任務
if (options.isSyncLoading()) {//如果是同步的,那麼displayTask立即執行
displayTask.run();
} else {//否則提交displayTask到任務佇列再執行
engine.submit(displayTask);
}
}
}
原始碼中進行了簡單註釋,整個載入流程就在這一個方法中。
我們將其中幾個重要步驟分解來看:
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根據Key從記憶體快取查詢是否有快取的bitmap
從記憶體快取中查詢快取的bitmap,我們看看memoryCache
的具體實現:
final MemoryCache memoryCache;
為ImageLoaderConfiguration
的成員變數,看看它在哪裡初始化的:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
private void initEmptyFieldsWithDefaultValues() {
...省略
if (memoryCache == null) {//如果沒有配置,則建立預設的記憶體快取
memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
}
...省略
}
我們再看看這個方法:
public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
if (memoryCacheSize == 0) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
if (hasHoneycomb() && isLargeHeap(context)) {//如果SDK版本為3.0以上,並配置了android:largeHeap="true",可以申請大記憶體
memoryClass = getLargeMemoryClass(am);
}
memoryCacheSize = 1024 * 1024 * memoryClass / 8;// 1/8可用記憶體
}
return new LruMemoryCache(memoryCacheSize);//預設實現為LruMemoryCache
}
LruMemoryCache
為介面MemoryCache
的實現類,作者為我們提供了多種MemoryCache
的實現類,來適應不用的記憶體快取需求:
有興趣的同學可以分析下這些快取實現,本文只分析LruMemoryCache
,其實該類個人理解是簡化版的LruCache
,看看原始碼:
/**
* A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
* the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
* become eligible for garbage collection.<br />
* <br />
* <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.1
*
* 快取了一定數量的Bitmap的強引用。當一個Bitmap被訪問時,它會移動到序列的隊尾。當快取滿時,再新增Bitmap,會將
* 序列頭部的Bitmap釋放掉,等待GC回收。
*
*/
public class LruMemoryCache implements MemoryCache {
//內部使用LinkedHashMap來儲存Bitmap
private final LinkedHashMap<String, Bitmap> map;
//最大快取的位元組數
private final int maxSize;
/** Size of this cache in bytes */
private int size;//當前快取的位元組數
/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
public LruMemoryCache(int maxSize) {
if (maxSize <= 0) {//檢查設定的快取大小,小於0丟擲異常
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//建立一個基於訪問順序的LinkedHashMap,設定false就是預設的插入順序,這裡不討論LinkedHashMap的實現了
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
}
/**
* Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
* of the queue. This returns null if a Bitmap is not cached.
* 根據key獲取快取的Bitmap,如果能夠獲取到,那麼該引用會移動到佇列尾部。如果沒有快取就返回Null
*/
@Override
public final Bitmap get(String key) {
if (key == null) {//檢查key是否為null,是null拋異常
throw new NullPointerException("key == null");
}
//同步,多執行緒訪問時保證執行緒安全
synchronized (this) {
return map.get(key);
}
}
/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue.
* put進的bitmap會放進隊尾
*/
@Override
public final boolean put(String key, Bitmap value) {
if (key == null || value == null) {//檢查key和value值,如果為null丟擲異常
throw new NullPointerException("key == null || value == null");
}
synchronized (this) {//開啟同步
size += sizeOf(key, value);//累計計算bitmap的大小
Bitmap previous = map.put(key, value);//加入map
if (previous != null) {//如果之前存在該快取的bitmap,那麼size就不再累計該bitmap的大小
size -= sizeOf(key, previous);//
}
}
//檢查size是否在合理的範圍內,如果不再做相應處理
trimToSize(maxSize);
return true;
}
/**
* Remove the eldest entries until the total of remaining entries is at or below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
*
* 該方法主要控制快取的bitmap不超過maxSize,一旦超過就移除最久沒用用過的bitmap,直到小於maxSize
*/
private void trimToSize(int maxSize) {
while (true) {//開啟迴圈
String key;
Bitmap value;
synchronized (this) {//開啟同步
if (size < 0 || (map.isEmpty() && size != 0)) {//檢查size或map是否正常,否則丟擲異常
throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
//如果size小於maxSize或者map是空的,那麼說明map狀態正常,結束迴圈
if (size <= maxSize || map.isEmpty()) {
break;
}
//查詢佇列頭部儲存的bitmap
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {//如果要移除的entry物件為null,那麼也結束迴圈
break;
}
//獲取key,和value
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);//移除對應的bitmap
size -= sizeOf(key, value);//重新計算size大小
}
}
}
/** Removes the entry for {@code key} if it exists.
* 根據Key移除對應的bitmap
*/
@Override
public final Bitmap remove(String key) {
if (key == null) {//檢查key是否為null,null丟擲異常
throw new NullPointerException("key == null");
}
synchronized (this) {//開啟執行緒同步
Bitmap previous = map.remove(key);//移除
if (previous != null) {//如果之前的bitmap存在,那麼重新計算size大小
size -= sizeOf(key, previous);
}
return previous;
}
}
@Override
public Collection<String> keys() {//獲取key的set集合
synchronized (this) {
return new HashSet<String>(map.keySet());
}
}
@Override//清空map
public void clear() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* Returns the size {@code Bitmap} in bytes.
* <p/>
* An entry's size must not change while it is in the cache.
* 計算size方法
*/
private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public synchronized final String toString() {
return String.format("LruCache[maxSize=%d]", maxSize);
}
}
設計還是比較清晰簡單的,主要是通過LinkedHashMap
來進行儲存。
如果記憶體快取中獲取不到bitmap,那麼將根據uri從本地或網路進行載入,所有的資訊都封裝在LoadAndDisplayImageTask
類中,該類實現了Runnable
介面,因此整個過程是在run
方法進行的:
public void run() {
if (waitIfPaused()) return;//載入任務是否暫停
if (delayIfNeed()) return;//載入任務是否延時
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock();//獲取鎖
Bitmap bmp;
try {
checkTaskNotActual();//判斷當前任務是否正常,view是否被回收,任務是否正常
//再次從記憶體中獲取快取的bitmap,個人理解是當請求任務很多時,很可能之前的執行緒,已經將圖片快取了,所以再次獲取
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();//獲取圖片在這個方法裡
if (bmp == null) return; // listener callback already was fired
checkTaskNotActual();//判斷當前任務是否正常
checkTaskInterrupted();//判斷任務是否中斷
//是否進行圖片預處理
if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}
//是否開啟了圖片快取,如果開啟了,那麼進行快取
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {//如果存在快取,那麼做標記,說明是記憶體快取
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}
//是否需要處理圖片
if (bmp != null && options.shouldPostProcess()) {
L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
checkTaskNotActual();//判斷當前任務是否正常
checkTaskInterrupted();//判斷任務是否中斷
} catch (TaskCancelledException e) {
fireCancelEvent();//如果捕捉到取消任務的異常,那麼呼叫取消監聽方法
return;
} finally {
loadFromUriLock.unlock();//釋放鎖
}
//封裝顯示圖片任務
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);//顯示圖片
}
這裡看下bmp = configuration.memoryCache.get(memoryCacheKey);
,如果if (bmp == null || bmp.isRecycled())
那麼就會走到bmp = tryLoadBitmap();
,我們看看這個方法:
/**
* 獲取圖片
* @return
* @throws TaskCancelledException
*/
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {//根據uri從本地快取獲取圖片的快取檔案
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;//如果圖片存在,做本地快取標記
checkTaskNotActual();//判斷任務是否正常
//根據檔案路徑,解析出bitmap
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
//判斷是否bitmap是否存在,如果不存在,那麼將從網路進行載入
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;//做網路標記
String imageUriForDecoding = uri;
//如果設定了本地快取開啟,tryCacheImageOnDisk()方法會去網路獲取圖片,並快取到本地
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();//檢測任務是否正常
//如果設定了本地快取開啟,那麼bitmap會從本地載入,如果沒有則從網路載入
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);//如果載入失敗,設定失敗監聽
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);//如果載入失敗,設定失敗監聽
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
通過上面的方法分析我們可以大致知道如果開啟了本地快取,那麼將會執行tryCacheImageOnDisk()
先下載圖片再本地快取,而如果沒用開啟快取,那麼會執行decodeImage(imageUriForDecoding)
去網路下載圖片,這裡我們分析下通過本地快取和網路載入兩種方式,主要了解下兩者的實現區別,看看tryCacheImageOnDisk()
方法:
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage();//下載圖片,並進行本地快取
if (loaded) {//判斷有沒有下載成功
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
// 如果設定了本地圖片快取的最大寬高,預設為0,重新設定圖片大小並再次本地快取
resizeAndSaveImage(width, height);
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
看看downloadImage()
方法:
private boolean downloadImage() throws IOException {
//根據uri來獲取流
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {//進行本地快取
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
getStream()
方法預設由BaseImageDownloader
物件實現:
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://content provider
return getStreamFromContent(imageUri, extra);
case ASSETS://assets 目錄
return getStreamFromAssets(imageUri, extra);
case DRAWABLE://圖片
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
根據uri的字首來選擇不同的載入方式,這裡我們看看從getStreamFromNetwork(imageUri, extra)`方法:
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);//建立一個HttpURLConnection物件
int redirectCount = 0;//重定向次數
//最大5次重定向
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {//獲取圖片流
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
if (!shouldBeProcessed(conn)) {//如果狀態碼不是200,那麼關閉流
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
//重新封裝流
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
至此,就從網路獲取到圖片流了,然後轉換為bitmap,然後進行本地快取,如果設定了diskCacheSize
或者diskCacheFileCount
,只要滿足任意一個條件,那麼就會用LruDiskCache
實現,否者用UnlimitedDiskCache
實現,先看看UnlimitedDiskCache
吧,其實該類UnlimitedDiskCache
,就是BaseDiskCache
,這個對本地快取沒有大小限制,所以看看BaseDiskCache
的實現:
//根據圖片uri,來儲存uri
@Override
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
File imageFile = getFile(imageUri);//獲取快取檔案
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);//臨時檔案
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
boolean savedSuccessfully = false;
try {//將bitmap儲存到臨時檔案
savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
} finally {
IoUtils.closeSilently(os);
//如果儲存成功,但重新命名沒有成功則儲存失敗
if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
savedSuccessfully = false;
}
//如果沒有儲存成功,則刪除臨時檔案
if (!savedSuccessfully) {
tmpFile.delete();
}
}
bitmap.recycle();
return savedSuccessfully;
}
/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
//根據uri獲取快取圖片檔案
protected File getFile(String imageUri) {
String fileName = fileNameGenerator.generate(imageUri);//生成檔名
File dir = cacheDir;//快取目錄
if (!cacheDir.exists() && !cacheDir.mkdirs()) {//如果快取目錄不存在,那麼就用備用快取目錄
if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
dir = reserveCacheDir;
}
}
return new File(dir, fileName);
}
主要就是這兩個方法了,還是比較簡單。
至於LruDiskCache
類的原始碼,內部其實是由JakeWharton的DiskLruCache
實現的,所以單獨另開一篇分析此類,本文分析到此為止。