Volley图片加载ImageLoader

Quibbler 2021-3-2 534

Volley图片加载ImageLoader


        在Volley加载网络图片中了解到使用ImageLoader进行加载图片,本文来看看ImageLoader源码如何结合RequestQueue实现网络图片加载的。

        源码分两部分看,一部分是从ImageLoaderget(...)方法开启图片加载请求,另一部分是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、批处理

        将cacheKeyBatchedImageRequest传入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);
                }
            }

        


不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com