Volley不仅可以进行普通的网络请求,还提供了一个简单的图片加载框架,下面这段代码展示了最普遍的使用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
,它的用法更为简单是:
- 在布局文件中声明一个
NetworkImageView
控件 - 获取控件实例
networkImageView.setDefaultImageResId(R.drawable.ic_default_icon);
networkImageView.setErrorImageResId(R.drawable.ic_error_icon);
networkImageView.setImageUrl("xxx.png",
imageLoader);
NetworkImageView
内部的实现其实仍然是使用了ImageLoader
,所以我们主要看一下ImageLoader
相关的代码
代码分析
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
是当出现错误时显示的占位图 - 也可以自定义一个
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) {
// 如果从缓存中找到,则直接回调onResponse
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// 生成一个bitmap = null的ImageContainer, 回调onResponse, 这样做的目的是显示占位图
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
imageListener.onResponse(imageContainer, true);
// 从InFlightRequest中查找是否有正在请求的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;
}
//创建一个新的请求,用于请求图片
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
- 确定当前是在主线程
- 生成缓存key, key的规则是“url + #W${maxWidth} + #H${maxHeight} + #S${scaleType.ordinal}”
- 从缓存中查找Bitmap1,如果找到则回调
ImageListener.onResponse
,并返回 - 如果缓存中没有找到,则先生成一个
bitmap = null
的ImageContainer
,然后回调ImageListener.onResponse
, 这样做的目的在于可以先让ImageView
显示占位图 - 从
inFlightRequests
中查找是否存在key对应的正在请求的Request - 创建一个新的请求,添加到队列
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);
}
});
}
可以看到,实际就是构建了一个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;
// 计算出合适的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;
//计算出合适的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));
}
}
}
从之前的开源项目学习之Volley(一)可以知道,Request
最重要的方法就是performNetworkResponse
,ImageRequest
的performNetworkResponse
直接调用的私有方法doParse
, 从doParse
的代码中可以看出其逻辑主要就是解析出合适的Bitmap
2.1.2 ImageLoader.onGetImageSuccess
onGetImageSuccess
会再调用batchResponse
, 这两个方法的逻辑总结起来就是: 将请求到的Bitmap放入缓存,之后获取到之前传入的ImageListener
,回调onResponse