#########################ImageRequest####ImageRequest####################################
对应ImageRequest跟StringRequest、JsonRequest是一样的,我们看下ImageRequest的源码
/**
* Creates a new image request, decoding to a maximum specified width and
* height. If both width and height are zero, the image will be decoded to
* its natural size. If one of the two is nonzero, that dimension will be
* clamped and the other one will be set to preserve the image's aspect
* ratio. If both width and height are nonzero, the image will be decoded to
* be fit in the rectangle of dimensions width x height while keeping its
* aspect ratio.
*
* @param url URL of the image
* @param listener Listener to receive the decoded bitmap
* @param maxWidth Maximum width to decode this bitmap to, or zero for none
* @param maxHeight Maximum height to decode this bitmap to, or zero for
* none
* @param decodeConfig Format to decode the bitmap to
* @param errorListener Error listener, or null to ignore errors
*/
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
构造器中的参数英文是如何解释的呢?以下是百度翻译给的解释:
创建一个新的图像请求,解码到一个最大指定的宽度和高度。如果两个宽度和高度都为零,图像将被解码为其自然大小。如果其中的一个是非零,则该维度将是钳位和另一个将被设置为保存图像的方面比。如果两个宽度和高度都是非零的,图像将被解码为在保持它的同时,在尺寸宽度x高度的矩形纵横比。
url、listener、errorListener不用解释了,跟StringRequest、JsonRequest含义一样
其他的参数为:图片的最大宽度,图片的最大高度,图片的颜色属性
构造器中还有一个请求失败重连的方法
setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/** Socket timeout in milliseconds for image requests */
private static final int IMAGE_TIMEOUT_MS = 1000;
/** Default number of retries for image requests */
private static final int IMAGE_MAX_RETRIES = 2;
/** Default backoff multiplier for image requests */
private static final float IMAGE_BACKOFF_MULT = 2f;
设定超时时间:1000ms;
最大的请求次数:2次;
发生冲突时的重传延迟增加数:2f(这个应该和TCP协议有关,冲突时需要退避一段时间,然后再次请求);
以下就是返回 Response<Bitmap>的代码
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;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// 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));
}
}
如果mMaxWidth == 0 && mMaxHeight == 0就返回原尺寸
如果不满足上述情况,就先获取图片的实际(注意是实际的)高度、宽度
int actualWidth = decodeOptions.outWidth; int actualHeight = decodeOptions.outHeight;
然后通过如下代码获取所需要的图片大小
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
我们就看看这个方法做了什么操作?
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
int actualSecondary) {
// If no dominant value at all, just return the actual.
if (maxPrimary == 0 && maxSecondary == 0) {
return actualPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0) {
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
if (resized * ratio > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
以上方法就是对图片的宽度、高度的实际大小根据需求的大小,进行宽度、高度的缩放,举个例子:
我的图片原本像素是:850x1200.
当maxWidth = 0,maxHeight = 0时,最终得到的bitmap的宽高是850x1200
当maxWidth = 0,maxHeight = 600时,得到的bitmap是425x600.这就说明它会按照一个不为0的边的值,将图片进行等比缩放。
当maxWidth = 100,maxHeight = 600时,我们得到的bitmap竟然是100x141,是按照100进行等比缩小后的图片,而不是100x600.
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
压缩完毕后,如果不合适, 通过createScaledBitmap再次处理到合适的大小,并且返回一个图片即可
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
###########################ImageLoader####ImageLoader####################################
我们都用过Volley的ImageLoader加载图片,比如如下的代码:
ImageLoader imageLoader2 = new ImageLoader(
MyApplication.getHttpRequestQueue(), new BitMapCache());
// view视图,默认的图片,错误的图片
ImageListener listener = imageLoader1.getImageListener(imageview2,
R.drawable.ic_launcher, R.drawable.ic_launcher);
imageLoader2.get(url, listener);
那我们就先看ImageLoader的构造器是如何进行初始化的,都做了哪些工作?
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
<span style="font-size:14px;"> }</span>
参数为:请求队列、还有内存缓存ImageCache对象
首先简单看下ImageCache接口对象,大概意思就是一个url对应一个图片段,put、get就像是map键值对的大概意思。
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
imageLoader2.get(url, listener); 我们就来看看get方法中的listener是个什么东东呢?
ImageListener listener = imageLoader1.getImageListener(imageview2,
R.drawable.ic_launcher, R.drawable.ic_launcher);
我们就根据方法看看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);
}
}
};
}
直接返回一个接口对象如下:
public interface ImageListener extends ErrorListener {
public void onResponse(ImageContainer response, boolean isImmediate);
}
这段代码的意思是:(百度翻译)
对imagelistener处理基本功能的默认实现,在接收到网络响应之前,显示一个默认图像,在哪个点,它将切换到实际的图像或错误图像。人工翻译的很清楚,通过参数也能知道个大概。
最后我们就可以看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) {
// 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;
}
getCacheKey为一个图片的url生成一个String类型的标识cacheKey(这个应该是),然后mCache.getBitmap(cacheKey);去缓存获取有没有数据,如果有的话
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
就在ImageListener 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);
}
}
};
}
mCache.getBitmap(cacheKey)如果缓存中没有对应的图片,就进行初始化一个ImageContainer(没有bitmap),然后直接回调,
imageListener.onResponse(imageContainer, true);
这里为了让大家在回调中判断,然后设置默认图片(所以,大家在自己实现listener的时候,别忘了判断resp.getBitmap()!=null)
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
imageListener.onResponse(imageContainer, true);
else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
BatchedImageRequest request = mInFlightRequests.get(cacheKey);判断同一个key的请求是否已经存在,查看当前执行队列中是否有与之相同的请求,如果有,那么加入到批处理请求队列当中,由这个请求队列去处理这些相同的请求
如果key请求不存在,就创建一个请求,去请求图片
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));
我们就看看这个请求中的onGetImageSuccess和onGetImageError做了什么处理操作?
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);
}
}
请求成功后,就存放到mCache中,方便下次取,(运用中我们可以结合LruCache),然后就remove掉cacheKey,remove方法返回那个请求,就进行判断,如果不为null,
if (request != null) {
request.setError(error);
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);
}
}
将获取到的图片
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
在去接口回调方法中去替换调那些请求前,显示的默认图片。
ImageLoader的代码还是比较复杂的,但是思路还是比较清晰的,总结如下:
1、通过ImageLoader的get方法获取图片,如果我们只想获取原始图片,不用关心大小,则只用传入url和Listen er,如果需要设置图片大小,那么传入你需要设置大大小
2、get方法中,先回去缓存中查找,如果命中,那么就直接放回,如果没有命中,那么就判断mInFlightRequests中是否有相同key的BatchedImageRequest,如果有则直接将 ImageConainer加入BatchedImageRequest的mContainres中,因为对于同一个key没有必要发送两次请求
3、如果在mInFlightRequest中没有此key,那么需要创建一个ImageRequest对象,并加入RequestQueue中,并使用ImageRequest创建一个BatchedImageRequest加入 mInFlightRequest
4、当请求返回后,将BatchedImageRequest从mInFlightRequest中移除,加入mBatchedResponses中,将返回结果返回给所有的ImageContainer
5、如果一个ImageContainer在收到返回结果之前就被cancel掉,那么需要将它从mInFlightRequest的mContainers中移除,如果移除后mContainers的size为0,说明这个请求只有
#############################NetworkImageView ########################################
我们看下public class NetworkImageView extends ImageView 跟ImageLoader有何联系?
我们学习demo时候是这样写的如下:
ImageLoader imageLoader1 = new ImageLoader(
MyApplication.getHttpRequestQueue(), new BitMapCache());
networkImageView.setDefaultImageResId(R.drawable.ic_launcher);
networkImageView.setErrorImageResId(R.drawable.ic_launcher);
networkImageView.setImageUrl(url, imageLoader1);
它的构造函数里面,也是初始化一个ImageLoader,说明NetworkImageView也是用了ImageLoader的获取图片机制
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);
}
既然是自定义的ImageView,所以说在控件的onLayout函数中布局完成,在调用这个函数
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
那我们就看看这个方法(有注释,就不说明文字了)
void loadImageIfNecessary(final boolean isInLayoutPass) {
//获取imageView控件的长度和宽度
int width = getWidth();
int height = getHeight();
boolean wrapWidth = false, wrapHeight = false;
if (getLayoutParams() != null) {
wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
}
/**
* 判断控件是否可用,比如宽度高度是否为0。。。
*/
boolean isFullyWrapContent = wrapWidth && wrapHeight;
if (width == 0 && height == 0 && !isFullyWrapContent) {
return;
}
/**
* 如果url为空,就默认加载一张图片
* private void setDefaultImageOrNull() {
if(mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
else {
setImageBitmap(null);
}
}
*/
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// 如果请求是相同的url,那么直接return,数据的获取由缓存处理
return;
} else {
//如果有一个预先存在的请求,请取消它,如果它是获取一个不同的网址
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
// 计算最大图像宽度/高度使用而忽略wrap_content尺寸.
int maxWidth = wrapWidth ? 0 : width;
int maxHeight = wrapHeight ? 0 : height;
/**
* 接下来就是跟imageloader里面的请求代码思路一样了
*/
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);
}
}
}, maxWidth, maxHeight);
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}
到此Volley学习(三)ImageRequest、ImageLoader、NetworkImageView源码简读了一遍,也算是有个大概了解,
下一篇,就来封装下图片请求的缓存使用及其工具类(关于图片的)