Volley不僅可以進(jìn)行普通的網(wǎng)絡(luò)請(qǐng)求,還提供了一個(gè)簡(jiǎn)單的圖片加載框架,下面這段代碼展示了最普遍的使用Volley加載圖片的方法
RequestQueue queue = Volley.newRequestQueue(context);
ImageLoader loader = new ImageLoader(queue, new ImageCache() {
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
@Override
public Bitmap getBitmap(String url) {
}
});
ImageListener listener = ImageLoader.getImageListener(imageView, R.drwable.ic_default_icon, R.drawable.ic_error_icon);
loader.get("xxx.jpeg", listener, 200, 200);
除了這種用法外,另一種比較常用的是NetworkImageView,它的用法更為簡(jiǎn)單是:
- 在布局文件中聲明一個(gè)
NetworkImageView控件 - 獲取控件實(shí)例
networkImageView.setDefaultImageResId(R.drawable.ic_default_icon);
networkImageView.setErrorImageResId(R.drawable.ic_error_icon);
networkImageView.setImageUrl("xxx.png",
imageLoader);
NetworkImageView內(nèi)部的實(shí)現(xiàn)其實(shí)仍然是使用了ImageLoader,所以我們主要看一下ImageLoader相關(guān)的代碼
代碼分析
1. ImageLoader.getImageListener
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);
}
}
};
}
-
defaultImageResId是占位圖,errorImageResId是當(dāng)出現(xiàn)錯(cuò)誤時(shí)顯示的占位圖 - 也可以自定義一個(gè)
ImgeListener
2. ImageLoader.get
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) {
//確定是在主線程
throwIfNotOnMainThread();
//生成緩存key
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
//從緩存中查找Bitmap
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// 如果從緩存中找到,則直接回調(diào)onResponse
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// 生成一個(gè)bitmap = null的ImageContainer, 回調(diào)onResponse, 這樣做的目的是顯示占位圖
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
imageListener.onResponse(imageContainer, true);
// 從InFlightRequest中查找是否有正在請(qǐng)求的Request
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
//創(chuàng)建一個(gè)新的請(qǐng)求,用于請(qǐng)求圖片
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
- 確定當(dāng)前是在主線程
- 生成緩存key, key的規(guī)則是“url + #W${maxWidth} + #H${maxHeight} + #S${scaleType.ordinal}”
- 從緩存中查找Bitmap1,如果找到則回調(diào)
ImageListener.onResponse,并返回 - 如果緩存中沒有找到,則先生成一個(gè)
bitmap = null的ImageContainer,然后回調(diào)ImageListener.onResponse, 這樣做的目的在于可以先讓ImageView顯示占位圖 - 從
inFlightRequests中查找是否存在key對(duì)應(yīng)的正在請(qǐng)求的Request - 創(chuàng)建一個(gè)新的請(qǐng)求,添加到隊(duì)列
2.1 ImageLoader.makeImageRequest
protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
ScaleType scaleType, final String cacheKey) {
return new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight, scaleType, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
}
可以看到,實(shí)際就是構(gòu)建了一個(gè)ImageRequest
2.1.1 ImageRequest
public class ImageRequest extends Request<Bitmap> {
...
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
return Response.error(new ParseError(e));
}
}
}
private Response<Bitmap> doParse(NetworkResponse response) {
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;
// 計(jì)算出合適的width和height
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth, mScaleType);
decodeOptions.inJustDecodeBounds = false;
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
//計(jì)算出合適的imSampleSize
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));
}
}
}
從之前的開源項(xiàng)目學(xué)習(xí)之Volley(一)可以知道,Request最重要的方法就是performNetworkResponse,ImageRequest的performNetworkResponse直接調(diào)用的私有方法doParse, 從doParse的代碼中可以看出其邏輯主要就是解析出合適的Bitmap
2.1.2 ImageLoader.onGetImageSuccess
onGetImageSuccess會(huì)再調(diào)用batchResponse, 這兩個(gè)方法的邏輯總結(jié)起來就是: 將請(qǐng)求到的Bitmap放入緩存,之后獲取到之前傳入的ImageListener,回調(diào)onResponse