imageLoader.get( url,
ImageLoader.getImageListener(iv, R.mipmap.aio_image_default, R.mipmap.aio_image_fail))
只要把图片地址、要显示的ImageView给到ImageLoader就可以自动帮你加载了,到底他是怎么实现的呢?我们一起到源码看看。
public interface ImageListener extends ErrorListener {
public void onResponse(ImageContainer response, boolean isImmediate);
}
public interface ErrorListener {
void onErrorResponse(VolleyError var1);
}
里面有两个没有实现的方法,分别是成功和失败的回调。这很好理解,下面看看怎么获取它:
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);
}
}
};
}
直接用了一个内部类把它给实现了,并且返回,我们只需要知道,当外界调取这个方法的时候,就获取了一个ImageListener对象。
2.异步执行
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// 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<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
这段代码有点长,我们一步一步看。首先,必须要在主线程调用这个方法;第二步,根据图片的url,长度和宽度去获取缓存中的key,如果,缓存中有的话,直接执行返回。这里注意的一点是这个缓存对象,Volley并没有帮我们实现,是一个接口:
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
需要我们自己实现,就和Collections中的排序方法一样用了一种策略设计模式,可以自己自定义排序方式:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] a = list.toArray();
Arrays.sort(a, (Comparator)c);
ListIterator i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set(a[j]);
}
}
我们这里也可以自己自定义缓存的具体实现;那么第三步,如果缓存中没有数据,那么先构建一个ImageContainer对象,并用imageListener对象去执行onResponse方法,让外界先用默认的图片显示;第四步,从mInFlightRequests对象获取正在执行的BatchedImageRequest,BatchedImageRequest对象封装了:
/** The request being tracked */
private final Request<?> mRequest;
/** The result of the request being tracked by this item */
private Bitmap mResponseBitmap;
/** Error if one occurred for this response */
private VolleyError mError;
/** List of all of the active ImageContainers that are interested in the request */
private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
很简单,不用详细说明了。至于mInFlightRequests这个对象和之前网络请求中的mWaitingRequests 对象非常相似,但又略有所不同。如果对网络请求源码不了解的,可以看我前面一篇Volley源码解析(一),同样,他也是为了防止同样的请求操作多次,但是他又把ImageListener给封装到了ImageContainer对象中去了。在详细看这段代码:
// 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;
}
为的是,当我们的同样的请求执行结束后,在用各自的ImageListener分发到各自的UI请求界面中,到后面代码就知道了。接下来第五步,如果缓存中不存在,请求又不是在航班上(执行中),那么我们就要去执行请求了:
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
就是使用了ImageRequest,至于请求结果我们稍后在看。最后一步所做的事情就是,将请求放到航班队列上,防止一个请求多次请求网络:
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
3.请求结果处理
请求成功或失败的代码都差不多,我们就看看执行成功了改做什么:
private 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);
}
}
首先,我们把得到的结果放入缓存中,然后把航班上的请求给移除了,表示请求执行结束。并把请求结果放到BatchedImageRequest对象中去。重点就是batchResponse方法了,点进去看看:
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);
}
}
到这里,想必很清楚了,把BatchedImageRequest中的结果分别用各自的ImageListener去执行返回结果。
/** * Sets the default image resource ID to be used for this view until the attempt to load it * completes. */
public void setDefaultImageResId(int defaultImage) {
mDefaultImageId = defaultImage;
}
/** * Sets the error image resource ID to be used for this view in the event that the image * requested fails to load. */
public void setErrorImageResId(int errorImage) {
mErrorImageId = errorImage;
}
当我们调用setImageUrl()方法的时候,就开始加载了。
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);
}
主要代码在loadImageIfNecessary,我们点进去
private void loadImageIfNecessary(final boolean isInLayoutPass) {
int width = getWidth();
int height = getHeight();
boolean isFullyWrapContent = getLayoutParams() != null
&& getLayoutParams().height == LayoutParams.WRAP_CONTENT
&& getLayoutParams().width == LayoutParams.WRAP_CONTENT;
// if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
// view, hold off on loading the image.
if (width == 0 && height == 0 && !isFullyWrapContent) {
return;
}
// 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;
}
// 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();
}
}
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
@Override
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);
}
}
});
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}
首先,如果获取不到NetworkImageView的长宽就return,什么都不做。然后如果传过来的url是空的,就放默认的图片到视图上。接下来这段代码也是一个缓存策略
// 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(); } }
至于我们的mImageContainer是它内部维护的一个对象,之前在看ImageLoader的时候已经看到它了,封装了执行结果,url,缓存key,ImageListener对象。那么我们调用了ImageLoader.get()的方法之后,会返回一个ImageContainer对象。好,继续看代码,如果mImageContainer中的请求url和传过来的url是一样的,那么就返回,不需要执行,因为结果都是一样的。否则,取消当前的请求,设置默认的图片,继续下面的操作,想都不用想,肯定是加载图片呗。
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
@Override
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);
}
}
});
但是这里值得注意的地方就是onResponse内部的方法:
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
这个是啥意思呢,其实就是当布局重绘的时候,调用NetworkImageView的onLayout()方法的时候
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
传入一个true进来,那么isInLayoutPass就是true了,如果满足要是立即要执行的条件时,就会执行里面的方法。想想也很简单么,因为,我们要执行完onDraw的时候,我们才可以给它设置图片麽,所以用Post放到MQ队列中,让其绘制完了后在调用onResponse方法,注意,此时,传入了一个false进来,那么就会执行底下的代码了。这个不需要我们的关心,一般,我们调用setImageUrl传进来的就是false。
当我们使用ImageLoader去异步执行请求的时候,会返回给我们ImageContainer,我们在使用这个ImageContainer的时候,要进行非空判断,因为它的内部维护的Bitmap可能为空
NetworkImageView使用起来虽然是很方面,但是如果大量的列表实现就不推荐使用NetworkImageView了,会占用大量的内存空间。