Volley图片加载ImageLoader
在Volley加载网络图片中了解到使用ImageLoader进行加载图片,本文来看看ImageLoader源码如何结合RequestQueue实现网络图片加载的。
源码分两部分看,一部分是从ImageLoader的get(...)方法开启图片加载请求,另一部分是NetworkDispatcher的网络请求结果回调。
1、图片加载请求
总体的请求逻辑如下,大部分代码都封装在ImageLoader中。
1.1、get请求
使用ImageLoader进行图片加载请求最简单的方法,传入请求Url链接,还有包装了ImageView的ImageListener请求监听器。
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, /* maxWidth= */ 0, /* maxHeight= */ 0);
}
调用另一个重载方法,当然开发者可以根据需要自行选择调用的get(...)重载方法,这里设置图片宽高以及图片放缩类型。
public ImageContainer get(
String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) {
return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
最终调用下面的get(...)方法。值得一提的是使用ImageLoader进行图片加载,必须在UI线程调用get(...),通过否则会抛出异常。
@MainThread
public ImageContainer get(
String requestUrl,
ImageListener imageListener,
int maxWidth,
int maxHeight,
ScaleType scaleType) {
//仅允许从主线程发起请求
Threads.throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// 先尝试在缓存中查找请求。
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container =
new ImageContainer(
cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
imageListener.onResponse(container, true);
return container;
}
// 缓存中不存在Bitmap,则从网络中获取
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;
}
ImageLoader图片加载请求方法逻辑清晰有条理,让我们分解开逐个看看代码片段。
1.2、先判断主线程
只能在主线程调用get(...)图片加载方法:
// only fulfill requests that were initiated from the main thread.
Threads.throwIfNotOnMainThread();
...
throwIfNotOnMainThread()方法判断当前运行线程是否是主线程的方法就是将当前线程的Looper和主线程的Looper对比是否是同一个Looper,关于Looper详见Handler、Looper、MessageQueue消息机制原理。
static void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("Must be invoked from the main thread.");
}
}
1.3、先查缓存
通过getCacheKey(...)方法构造缓存对应的Key
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
getCacheKey(...)方法构造Key通过请求连接、宽高等信息构造唯一的Key:
private static String getCacheKey(
String url, int maxWidth, int maxHeight, ScaleType scaleType) {
return new StringBuilder(url.length() + 12)
.append("#W")
.append(maxWidth)
.append("#H")
.append(maxHeight)
.append("#S")
.append(scaleType.ordinal())
.append(url)
.toString();
}
通过Key从ImageCache缓存中获取Bitmap缓存,ImageCache就是开发者自行实现的缓存。参考Volley加载网络图片中第2.1节。
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
如果缓存命中,那么直接构造ImageContainer对象,封装Bitmap缓存,通过ImageListener回调传递结果。
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container =
new ImageContainer(
cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
imageListener.onResponse(container, true);
return container;
}
如果缓存没有命中,那么就需要进行网络缓存。在进行网络请求前,得先给ImageView设置默认图片,这也是构造ImageListener中传入的参数defaultImageResId。
// 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);
1.4、网络请求复用
等等!还有一步,先从已有的请求队列mInFlightRequests中查看一下是否已经有相同的请求,如果有就复用,并将ImageContainer放入到请求结果BatchedImageRequest等待队列mContainers集合中。
// 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;
}
1.5、开启网络请求
最终,这是一个新的请求,需要构造ImageRequest,并将请求放入RequestQueue请求队列中处理。
// 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);
最后将构造的请求封装在BatchedImageRequest中,等待回调和复用请求。
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
2、图片请求回调
Volley网络请求加载源码在Volley之网络请求NetworkDispatcher一文中已经详细的解析过。直接看ImageLoader放入的图片加载请求回调逻辑。
2.1、Response.Listener<Bitmap>回调实现
在1.5节中构造的网络请求ImageRequest中传入Response.Listener<Bitmap>匿名对象,它的onResponse(Bitmap response)方法显示内部调用ImageLoader的onGetImageSuccess(String cacheKey, Bitmap response)方法
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);
}
});
}
2.2、分发回调
从ImageLoader中的请求队列mInFlightRequests中获取当前回调结果cacheKey对应的请求BatchedImageRequest。
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);
}
}
2.3、批处理
将cacheKey和BatchedImageRequest传入batchResponse(String cacheKey, BatchedImageRequest 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);
}
}
2.4、请求复用处理
BatchedImageRequest中可能包含很多重复的请求集合,因为在1.4节会将重复请求复用:如果请求的链接已经在请求队列中,那么将ImageContainer放入BatchedImageRequest中的mContainers集合中,等待相同网络请求返回一并回调处理,减少重复网络请求。
/**
* Adds another ImageContainer to the list of those interested in the results of the
* request.
*/
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
在这里循环依次回调所有重复的请求回调:
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());
}
}
2.5、完结
ImageContainer包装了ImageListener,而ImageListener是我们通过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);
}
}
};
}
于是在层层回调周转下,最终给ImageView设置了网络请求完毕的图片:
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}