android volley 源码,Android Volley库源码简析(Image Request部分)

本文仅对Volley中关于Image Request部分的一些简单用例做解析,Http Request部分请参考这里:Android Volley库源码简析(HTTP Request部分)

从常用case入手

用Volley请求图片有两种方式,通过ImageRequest或者是使用NetworkImageView。

使用ImageRequest

// 1. 新建一个queue

mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());

ImageView mImageView;

String url = "http://i.imgur.com/7spzG.png";

mImageView = (ImageView) findViewById(R.id.myImage);

// 2. 新建一个ImageRequest,传入url和回调

ImageRequest request = new ImageRequest(url,

new Response.Listener() {

@Override

public void onResponse(Bitmap bitmap) {

mImageView.setImageBitmap(bitmap);

}

}, 0, 0, null,

new Response.ErrorListener() {

public void onErrorResponse(VolleyError error) {

mImageView.setImageResource(R.drawable.image_load_error);

}

});

// 3. 将image request放到queue中

mRequestQueue.add(request);

使用的具体步骤见注释。可以看出image请求与普通http请求发送流程是一样的,只是Request接口的实现不同,其中最重要的是ImageRequest实现的parseNetworkResponse(NetworkResponse response)方法。此方法实现了从data到bitmap的转换。

使用NetworkImageView

// 1. 新建ImageLoader,传入queue和imagecache

mImageLoader = new ImageLoader(mRequestQueue,

new ImageLoader.ImageCache() {

private final LruCache

cache = new LruCache(20);

@Override

public Bitmap getBitmap(String url) {

return cache.get(url);

}

@Override

public void putBitmap(String url, Bitmap bitmap) {

cache.put(url, bitmap);

}

});

NetworkImageView mNetworkImageView;

private static final String IMAGE_URL =

"http://developer.android.com/images/training/system-ui.png";

...

mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);

// 2. 将url和ImageLoader传给NetworkImageView

mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

使用的具体步骤见注释。可以看到使用NetworkImageView比直接使用ImageView的代码量较少,而且最重要的一点是,使用者不用手动管理bitmap和image request的生命周期,当NetworkImageView被回收或者不可见的时候,bitmap资源会被回收,正在进行的image request会被cancel。

下面先对第一个用例进行分析。

ImageRequest

ImageRequest的初始化代码如下所示:

public class ImageRequest extends Request {

...

public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,

ScaleType scaleType, 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;

mScaleType = scaleType;

}

...

}

可以看到ImageRequest与普通http request的区别在于ImageRequest需要提供一些图片scale相关的参数,以供decode bitmap时使用,其中decode过程中最关键的parseNetworkResponse()方法源码如下所示:

public class ImageRequest extends Request {

...

private static final Object sDecodeLock = new Object();

...

@Override

protected Response parseNetworkResponse(NetworkResponse response) {

// Serialize all decode on a global lock to reduce concurrent heap usage.

synchronized (sDecodeLock) {

try {

return doParse(response);

} catch (OutOfMemoryError e) {

VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());

return Response.error(new ParseError(e));

}

}

}

为了避免network dispatcher同时decode bitmap引起的内存不足(OOM),这里使用了一个全局的lock来序列化decode操作。

private Response doParse(NetworkResponse response) {

byte[] data = response.data;

BitmapFactory.Options decodeOptions = new BitmapFactory.Options();

Bitmap bitmap = null;

// 如果mMaxWidth和mMaxHeight都为0,则按照bitmap实际大小进行decode

if (mMaxWidth == 0 && mMaxHeight == 0) {

decodeOptions.inPreferredConfig = mDecodeConfig;

bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

} else {

... // 根据mMaxWidth、mMaxHeight和scaleType来进行decode

}

if (bitmap == null) {

return Response.error(new ParseError(response));

} else {

// 最后将结果包装成Response返回给Delivery

return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));

}

}

...

}

其中decode相关的代码如下所示:

// 1. 先decode一次,求出图片的实际大小

decodeOptions.inJustDecodeBounds = true;

BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

int actualWidth = decodeOptions.outWidth;

int actualHeight = decodeOptions.outHeight;

// 2. 求出根据给定的参数的目标宽度和长度

int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,

actualWidth, actualHeight, mScaleType);

int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,

actualHeight, actualWidth, mScaleType);

// 3. 再用目标宽度和长度decode bitmap data

decodeOptions.inSampleSize =

findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);

Bitmap tempBitmap =

BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

// 4. 再次进行downscale

if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||

tempBitmap.getHeight() > desiredHeight)) {

bitmap = Bitmap.createScaledBitmap(tempBitmap,

desiredWidth, desiredHeight, true);

tempBitmap.recycle();

} else {

bitmap = tempBitmap;

}

scale的主要逻辑在getResizedDimension()和findBestSampleSize()中,这里不做详述。

ImageLoader

ImageLoader在RequestQueue的基础上套了一层memory cache,具体逻辑和RequesQueue很像。

先来看一下它的初始化方法:

public ImageLoader(RequestQueue queue, ImageCache imageCache) {

mRequestQueue = queue;

mCache = imageCache;

}

要传入一个RequestQueue和ImageCache,ImageCache是一个接口,代码如下所示:

public interface ImageCache {

public Bitmap getBitmap(String url);

public void putBitmap(String url, Bitmap bitmap);

}

不适用NetworkImageVIew的情况下,可以通过ImageLoader.get(String, 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, ScaleType scaleType) {

// get方法必须在主线程上运行

throwIfNotOnMainThread();

// 1. 生成cache key

final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

// 2. 检查mem cache是否命中

Bitmap cachedBitmap = mCache.getBitmap(cacheKey);

if (cachedBitmap != null) {

// 命中直接返回ImageContainer,并调用回调

ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);

imageListener.onResponse(container, true);

return container;

}

// 3. mem cache miss,新建ImageContainer

ImageContainer imageContainer =

new ImageContainer(null, requestUrl, cacheKey, imageListener);

// 4. 回调listener,这里应该显示default image

imageListener.onResponse(imageContainer, true);

// 5. 检查是否有相同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;

}

// 6. 新建ImageRequeue,放到Request中,其后步骤跟单独发起ImageRequest相同

Request newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,

cacheKey);

mRequestQueue.add(newRequest);

// 7. 标记当前执行的cache key

mInFlightRequests.put(cacheKey,

new BatchedImageRequest(newRequest, imageContainer));

return imageContainer;

}

protected Request makeImageRequest(String requestUrl, int maxWidth, int maxHeight,

ScaleType scaleType, final String cacheKey) {

return new ImageRequest(requestUrl, new Listener() {

@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);

}

});

}

其中onGetImageSuccess()的代码如下所示:

protected void onGetImageSuccess(String cacheKey, Bitmap response) {

// 8. 放到mem cache中

mCache.putBitmap(cacheKey, response);

// 9. request完成,清楚标记

BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

if (request != null) {

// 10. 更新BatchedImageRequest中的bitmap

request.mResponseBitmap = response;

// Send the batched response

batchResponse(cacheKey, request);

}

}

再来看一下batchResponse():

private void batchResponse(String cacheKey, BatchedImageRequest request) {

mBatchedResponses.put(cacheKey, request);

if (mRunnable == null) { // 在batch request被清空前只会进来一次

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;// 将runnable设为null才可以开始下一批reponse

}

};

// Post the runnable.

mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);

}

}

可以看出,batchResponse()的作用是确保某一个batch的第一个response能够在mBatchResponseDelayMs时间间隔后在主线程上执行。

onGetImageError的代码类似,这里不展开了。

NetworkImageView

使用NetworkImageVIew,非常方便:

mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

我们从setImageUrl开始分析。NetworkImageView继承于ImageView:

public class NetworkImageView extends ImageView {

private String mUrl;

private int mDefaultImageId;

private int mErrorImageId;

private ImageLoader mImageLoader;

private ImageContainer mImageContainer;

...

}

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()方法:

void loadImageIfNecessary(final boolean isInLayoutPass) {

int width = getWidth();

int height = getHeight();

ScaleType scaleType = getScaleType();

boolean wrapWidth = false, wrapHeight = false;

if (getLayoutParams() != null) {

wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;

wrapHeight = getLayoutParams().height == 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.

boolean isFullyWrapContent = wrapWidth && wrapHeight;

if (width == 0 && height == 0 && !isFullyWrapContent) {

return;

}

// 可以通过设置null url来清空imageview显示和cancel request

if (TextUtils.isEmpty(mUrl)) {

if (mImageContainer != null) {

mImageContainer.cancelRequest();

mImageContainer = null;

}

setDefaultImageOrNull();

return;

}

// 1. 如果当前view上有request正在执行,那么

if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {

if (mImageContainer.getRequestUrl().equals(mUrl)) {

// 如果url相同,那么忽略这次请求

return;

} else {

// 如果url不同,那么取消前一次

mImageContainer.cancelRequest();

setDefaultImageOrNull();

}

}

// 2. 计算maxWidth和maxHeight。如果view设置了LayoutParams.WRAP_CONTENT,那么不对maxWidth和maxHeight作限制

int maxWidth = wrapWidth ? 0 : width;

int maxHeight = wrapHeight ? 0 : height;

// 3. 调用ImageLoader的get方法发起image request

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) {

// 防止在layout过程中调用requestLayout方法

if (isImmediate && isInLayoutPass) {

post(new Runnable() {

@Override

public void run() {

onResponse(response, false);

}

});

return;

}

// 4. 拿到bitmap后,设置上去

if (response.getBitmap() != null) {

setImageBitmap(response.getBitmap());

} else if (mDefaultImageId != 0) {

setImageResource(mDefaultImageId);

}

}

}, maxWidth, maxHeight, scaleType);

// update the ImageContainer to be the new bitmap container.

mImageContainer = newContainer;

}

可见NetworkImageView也是利用了ImageLoader的get方法来实现图片下载。NetworkImageView的方便之处在于让我们不用手动管理图片资源的生命周期,和显示图片的状态切换(default, error状态)。

最后,看一下NetworkImageView自动回收资源是怎么实现的:

@Override

protected void onDetachedFromWindow() {

if (mImageContainer != null) {

// If the view was bound to an image request, cancel it and clear

// out the image from the view.

mImageContainer.cancelRequest();

setImageBitmap(null);

// also clear out the container so we can reload the image if necessary.

mImageContainer = null;

}

super.onDetachedFromWindow();

}

关键在于onDetachedFromWindow()的调用时机。由这篇文章可以了解到,onDetachedFromWindow()会在view被销毁,不再显示的时候调用。所以这样子做可以确保不会在view还在显示的状态下回收image资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值