当前位置

网站首页> 程序设计 > 开源项目 > 程序开发 > 浏览文章

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

作者:小梦 来源: 网络 时间: 2024-03-17 阅读:

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

从常用case入手

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

使用ImageRequest

// 1. 新建一个queuemRequestQueue = 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<Bitmap>() {        @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和imagecachemImageLoader = new ImageLoader(mRequestQueue,        new ImageLoader.ImageCache() {    private final LruCache<String, Bitmap>cache = new LruCache<String, Bitmap>(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传给NetworkImageViewmNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

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

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

ImageRequest

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

public class ImageRequest extends Request<Bitmap> {    ...    public ImageRequest(String url, Response.Listener<Bitmap> 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<Bitmap> {    ...    private static final Object sDecodeLock = new Object();    ...    @Override    protected Response<Bitmap> 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<Bitmap> 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返回给Deliveryreturn 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 datadecodeOptions.inSampleSize =    findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);Bitmap tempBitmap =    BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);// 4. 再次进行downscaleif (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<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,cacheKey);    mRequestQueue.add(newRequest);    // 7. 标记当前执行的cache key    mInFlightRequests.put(cacheKey,new BatchedImageRequest(newRequest, imageContainer));    return imageContainer;}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);        }    });}

其中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() {@Overridepublic 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() {@Overridepublic 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自动回收资源是怎么实现的:

@Overrideprotected 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资源。

热点阅读

网友最爱