本文將基于Android N Framework層中的Volley庫,對(duì)Volley中的圖片加載框架的源碼進(jìn)行分析
我們?cè)谏弦黄幸呀?jīng)對(duì)Volley的網(wǎng)絡(luò)庫工作流程做了進(jìn)行了簡(jiǎn)單分析,如果有不了解的朋友可以通過下面的鏈接進(jìn)行點(diǎn)贊匀归、關(guān)注和打賞友题。: )
Volley基于基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求框架封裝了自己的圖片請(qǐng)求框架,Volley中的圖片加載方式總結(jié)起來有三種冀惭,分別為ImageRequest、ImageLoader滑蚯、NetworkImageView丁稀,這三種方式名稱和使用方式各不相同,我們將通過每種加載方式的使用來對(duì)Volley的圖片的加載框架進(jìn)行分析但校。
Volley 圖片請(qǐng)求加載的三種方式
-
使用ImageRequest方式加載,其請(qǐng)求示例代碼如下:
RequestQueue mQueue = Volley.newRequestQueue(context); ImageRequest imageRequest = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { imageView.setImageBitmap(response); } }, 0, 0, Config.RGB_565, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { imageView.setImageResource(R.drawable.default_image); } }); mQueue.add(imageRequest);
-
使用ImageLoader方式加載,其請(qǐng)求示例代碼如下:
RequestQueue mQueue = Volley.newRequestQueue(context); ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() { @Override public void putBitmap(String url, Bitmap bitmap) { } @Override public Bitmap getBitmap(String url) { return null; } }); ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_image, R.drawable.failed_image); imageLoader.get(url, listener);
-
使用NetworkImageView方式加載,其請(qǐng)求示例代碼如下:
<com.android.volley.toolbox.NetworkImageView android:id="@+id/network_image_view" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center_horizontal" /> RequestQueue mQueue = Volley.newRequestQueue(context); ImageLoader imageLoader = new ImageLoader(mQueue,ImageCache); networkImageView = (NetworkImageView) findViewById(R.id.network_image_view); networkImageView.setDefaultImageResId(R.drawable.default_image); networkImageView.setErrorImageResId(R.drawable.failed_image); networkImageView.setImageUrl(url, imageLoader);
Volley ImageRequest 請(qǐng)求分析
方式1中ImageRequest的使用方式還是最常用的Request的使用方式,區(qū)別在于在deliverResponse的()結(jié)果不同(具體請(qǐng)求邏輯請(qǐng)參考文章頭部的鏈接文章)。Volley的網(wǎng)絡(luò)請(qǐng)求結(jié)果,如果緩存命中,則會(huì)調(diào)用如下方法對(duì)結(jié)果進(jìn)行解析:
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
如果緩存未命中,通過NetworkDispatcher進(jìn)行網(wǎng)絡(luò)請(qǐng)求,則會(huì)調(diào)用如下方法對(duì)結(jié)果進(jìn)行解析:
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
不管是緩存命中(CacheDispatcher)還是不命中(NetworkDispatcher),都會(huì)通過request.parseNetworkResponse(response)
的方式將網(wǎng)絡(luò)結(jié)果的解析回調(diào)到Request中每强。則對(duì)于Request的子類實(shí)現(xiàn)ImageRequest,其解析方法實(shí)現(xiàn)如下代碼所示:
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
其中doParse()會(huì)進(jìn)行對(duì)reponse的字節(jié)留進(jìn)行處理,按請(qǐng)求參數(shù)包裝成相應(yīng)的Bitmap,其關(guān)鍵代碼如下所示始腾。
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth, mScaleType);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
注意這段解析代碼中maxWidth與maxHeight是用戶初始化傳入的請(qǐng)求參數(shù),如果調(diào)用方不傳的話,默認(rèn)為0,不進(jìn)行縮放。則解析的bitmap是原圖的尺寸大小,而如果調(diào)用方傳入?yún)?shù),則需要對(duì)圖片進(jìn)行縮放(PS:這段縮放代碼其實(shí)也是很有參考意義的)空执。在縮放完成后,將請(qǐng)求結(jié)果轉(zhuǎn)入到Response對(duì)象中返回浪箭。之后,通過ResponseDelivery調(diào)用postResponse(reqeust, response)將結(jié)果回調(diào)到Listener的時(shí)候,通過deliverResponse回調(diào)的就是具體的解析結(jié)果了(從這里我們也可以看出,如果有自定義Request需要的時(shí)候,只需要繼承Request,重寫parseNetworkResponse與deliverResponse方法即可實(shí)現(xiàn)Volley中Request的自定義結(jié)果解析與回調(diào))。
Volley ImageLoader 請(qǐng)求分析
ImageLoader中只有一個(gè)構(gòu)造函數(shù),它需要傳入RequestQueue(我們從這點(diǎn)可以猜想ImageLoader可能也會(huì)依賴ImageReqeust進(jìn)行請(qǐng)求)和ImageCache對(duì)象,而ImageCache是定義于ImageLoader類中的接口辨绊。
對(duì)于ImageCache說明如下源碼所示:
/**
Simple cache adapter interface. If provided to the ImageLoader, it
will be used as an L1 cache before dispatch to Volley. Implementations
must not block. Implementation with an LruCache is recommended.
*/
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
這段代碼的說明中注釋說的很清楚奶栖,大意為ImageCache是一個(gè)簡(jiǎn)單的緩存適配接口,它提供給Volley作為一級(jí)緩存,其實(shí)現(xiàn)不能產(chǎn)生阻塞。推薦使用LruCache來實(shí)現(xiàn)這個(gè)接口门坷。在上面的示例中,使用ImageCache直接返回了NULL,所以并沒有起到緩存的效果宣鄙。
ImageListener作為Imageloader中的一個(gè)接口,其繼承了Response中的ErrorListener,從這個(gè)角度也可以看的出,Imageloader底層是通過Request執(zhí)行的請(qǐng)求。而對(duì)于ImageListener的獲取,如下代碼所示:
public static ImageListener getImageListener(final ImageView view,
final int defaultImageResId, final int errorImageResId) {
return new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
};
}
我們從默認(rèn)實(shí)現(xiàn)也可以看的出,在response回調(diào)中會(huì)去Response中獲取Bitmap,所以我們可以做一個(gè)推論:ImageLoader是通過ImageReqeust請(qǐng)求并對(duì)請(qǐng)求結(jié)果進(jìn)行封裝的一種圖片加載方式默蚌。接下來我們對(duì)ImageLoader的加載過程進(jìn)行詳細(xì)分析冻晤。在為ImageLoader綁定注冊(cè)監(jiān)聽回調(diào)后,我們可以使用ImageLoader的get方式加載圖片。其中g(shù)et有三種多態(tài)方法,如下所示:
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, 0, 0);
}
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ScaleType scaleType) {
...
}
我們可以看到,其中最終都是通過第三種方式進(jìn)行獲取,其中依賴傳入?yún)?shù)數(shù):url,listener,maxWidth,maxHeight,scaleType,如果不傳ScaleType,默認(rèn)的ScaleType為CenterInside绸吸。在ImageLoader通過get方式提交請(qǐng)求后鼻弧,會(huì)首先到設(shè)置的一級(jí)緩存中查詢圖片緩存设江,如果緩存沒有命中,則將request加入到RequestQueue中開始一個(gè)新請(qǐng)求攘轩,同時(shí)在mInFlightRequests中將請(qǐng)求加到請(qǐng)求隊(duì)列中叉存。源碼如下:
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ScaleType scaleType) {
// only fulfill requests that were initiated from the main thread.
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
PS:這段代碼會(huì)檢查是否運(yùn)行在主線程,如果運(yùn)行在子線程中度帮,則會(huì)直接拋出異常的歼捏。
其中,ImageContainer是ImageLoader的內(nèi)部類,在源碼中的注釋對(duì)它的介紹是:Container object for all of the data surrounding an image request.
,意思是一個(gè)ImageRequest的數(shù)據(jù)對(duì)象包裝類笨篷,它作為get方法的返回值瞳秽,當(dāng)有緩存的時(shí)候,它會(huì)將數(shù)據(jù)取出冕屯,直接回調(diào)同時(shí)返回包裝類寂诱。而若不存在緩存拂苹,則先創(chuàng)建ImageContainer,回調(diào)listenert通知顯示設(shè)置的默認(rèn)圖片安聘。
接著這里會(huì)出現(xiàn)一個(gè)新的類型BatchedImageRequest,還有一個(gè)類型為HashMap的mInFlightRequests瓢棒,mInFlightRequests是用來保存當(dāng)前已存在的Request,若有相同請(qǐng)求時(shí)浴韭,它會(huì)將ImageContainer加入到BatchedImageRquest中,而BatchedImageRequest是ImageLoader的內(nèi)部類脯宿,它是Request的包裝類念颈,其中有一個(gè)LinkedList去存儲(chǔ)ImageContainer。
當(dāng)有新的請(qǐng)求连霉,并將請(qǐng)求請(qǐng)求加入到請(qǐng)求隊(duì)列中的時(shí)候榴芳,就進(jìn)入了Volley的網(wǎng)絡(luò)請(qǐng)求邏輯中,這個(gè)我們前文已經(jīng)分析過跺撼,這里就不再贅述窟感。
在請(qǐng)求響應(yīng)后(以響應(yīng)成功為例),則會(huì)遍歷所有的ImageContainer歉井,回調(diào)到batchedRequest耦合的ImageContainer注冊(cè)的ImageListener中柿祈。部分代碼如下:
protected void onGetImageSuccess(String cacheKey, Bitmap response) {
// cache the image that was fetched.
mCache.putBitmap(cacheKey, response);
// remove the request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// Send the batched response
batchResponse(cacheKey, request);
}
}
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
在加載完成后,將緩存中的Request清空哩至,這樣整個(gè)請(qǐng)求就完成了躏嚎。但這里還有一個(gè)問題,ImageLoader中默認(rèn)構(gòu)建的ImageListener的onResponse回調(diào)方法如下所示:
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
其中的isImmediate參數(shù)并未使用到菩貌,這個(gè)參數(shù)從其注釋定義有這樣的表述:isImmediate True if this was called during ImageLoader.get() variants.This can be used to differentiate between a cached image loading and a network image loading in order to, for example, run an animation to fade in network loaded images.大意是指:這個(gè)參數(shù)如果是true,表明listener被調(diào)用是通過ImageLoader.get(),可以用來被區(qū)別圖片是來自緩存還是網(wǎng)絡(luò)加載卢佣。比如說在加載網(wǎng)絡(luò)圖片時(shí)來做一個(gè)淡入動(dòng)畫。但是我目前沒看出這個(gè)參數(shù)有什么具體的實(shí)際意義箭阶,如果大家有什么想法可以在留言中提出虚茶。
Volley NetworkImageView 圖片加載分析
對(duì)于第三種使用NetworkIMageView請(qǐng)求方式進(jìn)行的圖片加載,我們可以看到,在配置文件中配置后,卻是使用ImageLoader進(jìn)行加載的.在初始化NetworkImage后,設(shè)置好url就開始加載邏輯了晚缩。
public void setImageUrl(String url, ImageLoader imageLoader) {
mUrl = url;
mImageLoader = imageLoader;
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary(false);
}
調(diào)用loadImageIfNecessary()方法進(jìn)行圖片加載,而這個(gè)方法在NetworkImage中的onlayout中同樣會(huì)調(diào)用代碼如下所示(我沒想明白有什么場(chǎng)景是需要在在onLayout的時(shí)候去加載圖片的╮(╯▽╰)╭):
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
下面分析loadImageIfNecessary方法,我們可以看到如果傳入url為空,則調(diào)用setDefaultImageOrNull()方法設(shè)置默認(rèn)圖片,否則的話才會(huì)繼續(xù)加載圖片。
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
假如有相同的圖片在加載媳危,則會(huì)直接返回荞彼。否則的話,會(huì)將之前的那個(gè)Request取消待笑,重新加載現(xiàn)有請(qǐng)求鸣皂。
// if there was an old request in this view, check if it needs to be canceled.
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// if the request is from the same URL, return.
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
最后,這些條件都不滿足暮蹂,則通過mImageLoader.get()使用ImageLoader
進(jìn)行請(qǐng)求加載寞缝,這樣就進(jìn)入了ImageLoader的加載邏輯。這里仰泻,我們也找到了上述疑惑的地方荆陆,在NetworkImageView中創(chuàng)建的ImageListener 回調(diào)中有如下邏輯:
public void onResponse(final ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
} else if (mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
}
對(duì)于這段中的邏輯理解不了╮(╯▽╰)╭,有朋友有思路可以留言解惑集侯。
結(jié)語
我們可以看出,ImageLoader對(duì)于圖片邏輯的處理主要依賴于Request與RequestQueue框架,雖然整體使用較為繁瑣被啼,但是Volley對(duì)相關(guān)設(shè)置預(yù)留了擴(kuò)展,總體來說如果使用Volley做網(wǎng)絡(luò)庫棠枉,但是又不想引入其他圖片框架加大包體積的話浓体,使用Volley來做圖片加載也是一種不錯(cuò)的選擇。
對(duì)于網(wǎng)絡(luò)請(qǐng)求緩存的源碼分析已經(jīng)更新辈讶,詳情點(diǎn)擊:
PS:整體層次圖命浴,僅供參考,如果不足贱除,歡迎指出O(∩_∩)O哈生闲!