Android Volley 简单分析

       Android在做网络请求的时候经常会用到或者听到Volley框架,Volley框架主要用来和网络打交道,适合做一些简单快速的网络请求,并不适合做文件的上传和下载这类数据量比较大的请求。Volley源码完全开放我们可以在现有代码的基础之上根据我们特定的需求做不同程度的修改。

一. Volley的功能有

  • 同步,异步请求。(同步请求通过RequestFuture来实现)
  • 设置请求的优先级。(Request类的getPriority()函数)
  • 缓存请求结果。
  • 取消指定的请求。(给每个请求任务设置tag,比如给同一个Activity中的请求任务设置同一个tag,在Activity的onDestroy()方法中取消tag对应的所有的请求任务)

二. Volley的使用流程

  • 第一步:声明一个RequestQueue对象。(因为我们尽量一个应用只有一个RequestQueue实例,所以经常把这步放到Application中,Volley.newRequestQueue(this))
  • 第二步:声明一个请求任务对象Request。(我们要自己去实现Request,按照请求到的数据的格式Volley已经帮我们实现了StringRequest,JsonRequest,JsonArrayRequest,JsonObjectRequest,ImageRequest。如果这些还不能满足我们的需求那就得自己动手了)
  • 第三步:把请求对象添加到RequestQueue里面去。Volley后台的调度工作就开始工作了。
  • 第四步:结果传递到第二步Request里面设置的Listener,或者ErrorListener对应的函数里面。
    整个的请求任务就这样完成了。

三. Volley框架实现简单分析

1. Volley简单框架图

这里写图片描述

2. Volley整体文字上的简单概述

对应Volley的使用流程,简单看下每一步的流程里面做的一些事情

  • 第一步:声明一个RequestQueue对象:
           虽然只是简简单单的一句话,背后做的事情却蛮多的。会生成两个请求队列,缓存请求队列和网络请求队列;启动CacheDispatcher线程(CacheDispatcher作用于缓存请求队列,每次去缓存请求队列里面取出请求任务,先尝试去缓存中获取结果,如果结果没问题则把结果通过ExecutorDelivery传递出来,如果缓存得到的结果不可用则把对应的请求任务重新添加到网络请求队列);默认启动四个NetworkDispatcher线程(NetworkDispatcher作用于网络请求队列,每次都去网络请求队列里面取出请求任务,指导请求任务通过BasicNetwork去网络上面获取数据,获取数据之后如果有必要缓存则把数据保存到缓存,然后把结果通过ExecutorDelivery传递出来)。
  • 第二步:声明一个请求任务对象Request:
           Volley中按照数据形式的不同已经默认为我们实现了StringRequest,JsonRequest,JsonArrayRequest,JsonObjectRequest,ImageRequest这些请求任务,如果这些数据类型满足不了我们的需求,那就得自己动手了,在自定义请求任务的时候,重写parseNetworkResponse()函数把网络请求到的数据转换为我们需要的类型;重写deliverResponse()函数把正确的请求传递出来;同时还可能重写getParams()【比如我们的请求类型是post,恰好请求参数是键值对形式】或者getBody()【比如我们的请求类型是post,但是请求参数是json形式】。
  • 第三步:把请求对象添加到RequestQueue里面:
           把第二步定义的请求任务添加到第一步的RequestQueue里面去。会先去判断请求任务是否缓存,如果是则添加到缓存请求队列里面让CacheDispatcher线程去调度,如果不是则添加到网络请求队列里面让NetworkDispatcher线程去调度。在成功获取了结果之后通过ExecutorDelivery把请求结果传递出来(默认是传递到UI线程当中)。
3. Volley代码层次的简单分析

按照上面提到的Volley简单框架图从上往下简单的看看代码的实现过程。

Request类(请求任务)

       Request是一个抽象类,某种意义上来说Request只是制定了一些被调用的规则。要重新定义一个子类来继承Request类,Volley框架里面按照需要的数据格式的不同已经给定义了StringRequest,JsonRequest,JsonArrayRequest,JsonObjectRequest,ImageRequest。准备从以下几个方面来分析Request类。

1. Request做的工作如下:
  • 网络请求信息的封装,比如GET请求会把参数封装在url当中,POST请求则会去重写getParams()函数(比如请求参数是键值对的形式),或者去重写getBody()函数(比如请求参数是JSON的形式)等。
  • 设置请求有异常的情况下重新请求的一些规则(主要是设置重新请求的次数,和每次去重新请求的timeout)。
  • 设置网络请求的优先级(先判断getPriority(),接着判断setSequence())。
  • 设置是否缓存。
  • 取消网络请求。(如果该请求还在队列里面还没有被处理,这个时候这条请求是可以取消的)。
  • 在网络请求结束的情况下把请求到的结果传递出来。因为请求数据的时候不管是去缓存拿数据还是去网络拿数据都是在后台线程处理。我们要把结果Post到UI线程中来。(这部分的工作是通过ExecutorDelivery类来完成的会调用到Request里面的deliverResponse()或者deliverError()函数)。
2. Request类中一些函数的介绍(函数的一些简单的解释直接写在代码里面了)
    /**
     * 获取网络请求方式(最常见的有GET,POST。请求方式在构造函数里面传入)
     */
    public int getMethod()

    /**
     * 设置和获取网络请求的TAG,用于标识网络请求,在取消网络请求的时候大有用处
     */
    public Request<?> setTag(Object tag)
    public Object getTag()

    /**
     * 获取网络失败的监听(会在网络请求失败的情况下调用,请求失败的监听会在构造函数的时候传入)
     */
    public Response.ErrorListener getErrorListener()

    /**
     * 设置和获取一个tag,这个tag是用来标记线程(NetworkDispatcher的分发线程中会使用到)
     */
    public int getTrafficStatsTag()
    private static int findDefaultTrafficStatsTag(String url)

    /**
     * 设置和获取当网络请求异常的时候,重试的规则(volley提供了DefaultRetryPolicy,大概就是去设置重试的次数,改变每次重试时候的timeout)
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy)
    public RetryPolicy getRetryPolicy()

    /**
     * volley调试用的,可用打印调试信息,方便查看每个请求的执行情况。
     */
    public void addMarker(String tag)

    /**
     * 当一个网络请求结束的时候调用,同时这个函数里面会调用RequestQueue的finish函数,把RequestQueue队列里面对应的请求移除掉(请求结束了)
     */
    void finish(final String tag)

    /**
     * 设置这个请求关联的队列(RequestQueue)
     */
    public Request<?> setRequestQueue(RequestQueue requestQueue)

    /**
     * 设置和获取这个请求的序列(请求添加到队列去的时候会按顺序生成序列)。当请求的优先级等级相同的时候会根据这个判断请求的优先级(判断哪个请求先处理)
     */
    public final Request<?> setSequence(int sequence)
    public final int getSequence()

    /**
     * 获取请求对应的url。(url会在构造函数中传入)
     */
    public String getUrl()

    /**
     * 获取缓存的key,缓存机制会根据这个key去获取指定的缓存(volley默认使用的就是url,这里有一个疑问,如果是post请求,url可能是一样的这样缓存感觉有点问题)
     */
    public String getCacheKey()

    /**
     * 设置和获取缓存
     */
    public Request<?> setCacheEntry(Cache.Entry entry)
    public Cache.Entry getCacheEntry()

    /**
     *  取消网络请求。(就是去设置一个是否取消的状态,当从请求队列里面读到这个请求之后先判断是否是取消的请求,如果是不继续处理)
     */
    public void cancel()
    public boolean isCanceled()

    /**
     * 获取http请求的头部信息(当我们的请求需要head信息的时候,需要实现自己的)
     */
    public Map<String, String> getHeaders() throws AuthFailureError

    /**
     * 这个方法以及被弃用了,用getParams()代替
     */
    protected Map<String, String> getPostParams() throws AuthFailureError

    /**
     * 这个方法以及被弃用了,getParamsEncoding()代替
     */
    protected String getPostParamsEncoding()

    /**
     * 这个方法以及被弃用了,getBodyContentType()代替
     */
    public String getPostBodyContentType()

    /**
     * 这个方法以及被弃用了,getBody()代替
     */
    public byte[] getPostBody() throws AuthFailureError

    /**
     * 当我们要提交请求的参数内容要放在body里面的时候(比如post方式),并且提交的参数内容是键值对的形式的时候会在这里实现。
     */
    protected Map<String, String> getParams() throws AuthFailureError

    /**
     * 请求参数的编码格式(默认utf-8) 正好对应getParams里的编码格式
     */
    protected String getParamsEncoding()

    /**
     * 设置http Content-type请求格式信息。(默认是"application/x-www-form-urlencoded; charset=UTF-8")
     */
    public String getBodyContentType()

    /**
     * http请求的body信息。(如果是post方式请求的时候,如果请求的参数是键值对的形式则重写getParams()函数,如果参数是json的形式则重写getBody())
     * 如果没有重写getBody()函数的时候会使用getParams()里面提供的键值对的参数来生成http的body信息。
     */
    public byte[] getBody() throws AuthFailureError

    /**
     * 把键值对信息按照http的要求转换为byte数组(etParams()里面提供的键值对转换为http的body信息就是用这个函数来转换的)
     */
    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding)

    /**
     * 设置和获取是否可以换成数据,如果true,请求在加入队列的时候会先加入到缓存请求的队列里面去,如果缓存队列里面判断没有缓存或者缓存过期又会把这个请求
     * 加入到网络请求里面去,并且在请求成功之后会把数据通过缓存机制放入到缓存当中去。
     */
    public final Request<?> setShouldCache(boolean shouldCache)
    public final boolean shouldCache()

    /**
     * 设置和获取在请求过程中服务器出错的时候是否启用重新连接的机制。
     */
    public final Request<?> setShouldRetryServerErrors(boolean shouldRetryServerErrors)
    public final boolean shouldRetryServerErrors()

    /**
     * 设置往前请求的优先级,提供四种优先级从低到高依次是LOW,NORMAL,HIGH,IMMEDIATE(这里要注意在请求从队列里面去出来的时候优先判断优先级,
     * 当优先级相同的时候会去判断getSequence()取出来的值)
     */
    public Priority getPriority()

    /**
     * http请求的时候socket的超时时间。(这里特别想提的就是上文中多次说到的setRetryPolicy(),和getRetryPolicy()在http请求有异常的时候没有尝试
     * 重新连接的时候会把time out拉长。最终的体现还是在这里)
     */
    public final int getTimeoutMs()

    /**
     * 标记和获取这个请求是否已经请求结束了并且请求到的结果也已经传递出去了(也不管请求是成功了还是失败了反正ExecutorDelivery都传递下去了)
     * 为什么会有这个呢,有这么个情况请求从缓存中拿到数据了之后当需要刷新的时候,处理是先把缓存的结果传递出去,然后把
     * 这个请求放到网络队列中,网络队列拿出来之后去网络请求数据。如果请求道的数据没有改变,并且之前已经传递出去则
     * 不用在重复传递了。
     */
    public void markDelivered()
    public boolean hasHadResponseDelivered()

    /**
     * 解析网络请求正确的结果(因为不知道你要解析到的是什么对象的数据可能是String,Post对象等,所以直接干脆直接给你自己去实现)
     */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response)

    /**
     * 解析网络请求错误的结果
     */
    protected VolleyError parseNetworkError(VolleyError volleyError)

    /**
     * 反馈正确请求的结果。(一般的做法是会声明一个正确请求的listener。然后在这个函数里面调用listener对应的函数)
     */
    abstract protected void deliverResponse(T response)

    /**
     * 反馈错误请求的信息
     */
    public void deliverError(VolleyError error)

    /**
     * 从队列中取出来的时候通过这个函数来判断出队的顺序(可以看到是先判断优先级,在优先级相同的情况下判断的是getSequence()对应的值)
     */
    public int compareTo(Request<T> other)
3. 自定义Request,当Volley实现的Request满足不了我们需求的时候,就得自己动手干了

在定义一个自己的Request的时候,主要要注意的地方是

  • 如果我们网络请求的方式GET的方式,那么参数在url里面就给定了。
  • 如果我们网络请求的方式POST的方式,那么还得根据post的参数是不是键值对的形式如果是重写getParams()函数指定参数,如果参数是json的形式则是重写getBody()函数指定参数。(具体请求具体处理不是绝对的哦)。
  • 声明一个Response.Listener listener的对象,方便把正确的结果传递出来。
  • 重写parseNetworkResponse()函数,在网络请求正确的情况下把数据转换为我们需要的格式或者对象。
  • 重写deliverResponse()函数,正好调用上面调用Response.Listener listener对象的相关函数,把正确请求结果传递出来。(感觉这里应该算是一个可以优化的地方吧,总觉得这个的处理可以像Response.ErrorListener样的放到抽象类Request顶层去处理。这样就不用每个都要去重写这个方法)。

对于自定义一个Request,举一个简单的例子,背景是去自定义一个WeatherRequest的请求获取天气信息,把从网络上面获取到的数据放到WeatherInfo的一个bean对象里面去,代码如下(解释也直接在代码里面写了哦)

    /**
     * 一个简单的请求,一个网络请求(http://wthrcdn.etouch.cn/weather_mini?city=%E9%AB%98%E5%AE%89)去获取天气数据,把获取到的数据封装到WeatherInfo对象中去。
     */
    public class WeatherRequest extends Request<WeatherInfo> {

        /**
         * 定义网络请求成功时候的监听回调
         */
        private final Response.Listener<WeatherInfo> mListener;

        public WeatherRequest(int method, String url, Response.Listener<WeatherInfo> listener, Response.ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }

        public WeatherRequest(String url, Response.Listener<WeatherInfo> listener, Response.ErrorListener errorListener) {
            this(Method.GET, url, listener, errorListener);
        }

        /**
         * 重写parseNetworkResponse()函数,把请求正确获取来的数据转换为WeatherInfo对象。(这里使用了google提供的Gson库来转换)
         */
        @Override
        protected Response<WeatherInfo> parseNetworkResponse(NetworkResponse response) {
            try {
                String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8"));
                WeatherInfo weatherInfo = new Gson().fromJson(jsonString, new TypeToken<WeatherInfo>() {
                }.getType());
                return Response.success(weatherInfo, HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 在网络请求正确的情况下把结果通过回调传递出去
         */
        @Override
        protected void deliverResponse(WeatherInfo response) {
            mListener.onResponse(response);
        }
    }
4. 同步请求的实现(RequestFuture)

       虽然大部分情况下我们都用异步请求,必要的时候可能需要同步的请求,这个时候RequestFuture就派上大用场了。

       RequestFuture简单说明,RequestFuture实现了Future, Response.Listener, Response.ErrorListener三个接口。肯定重点关注onResponse(),onErrorResponse()以及Future相关的一些get()函数了。

    @Override
    public synchronized void onResponse(T response) {
        mResultReceived = true;
        mResult = response;
        notifyAll();
    }

    @Override
    public synchronized void onErrorResponse(VolleyError error) {
        mException = error;
        notifyAll();
    }

       网络请求结束的时候会调用到这两个函数,然后通过Future的get函数去拿到结果,达到同步请求的目的。
Volley同步请求简单步骤如下(要在线程中调用):

  • 声明一个RequestFuture对象。比如RequestFuture future = RequestFuture.newFuture()。
  • 声明请求对象。比如StringRequest request = new StringRequest(url, future, future)。
  • 添加到Volley的请求队列。
  • RequestFuture获取结果。future.get()获取结果。
RequestQueue类(请求队列)

       RequestQueue是我们经常所到的请求队列,其实他有两个PriorityBlockingQueue类型的队列;一个是缓存请求队列mCacheQueue(先到缓存里面去拿数据,缓存拿不到数据则把请求队列重新放到网络请求队列里面去),一个是网络请求队列mNetworkQueue。PriorityBlockingQueue保证了请求队列出队的时候有优先级。当要发起一个请求的时候通过RequestQueue的add()函数把这个请求添加到请求队列里面去然后让缓存队列的调度CacheDispatcher或者网络调度NetworkDispatcher去处理这个请求。

1. RequestQueue主要参数的介绍
    /**
     * 已经提交上来的请求,但是还没有添加到请求队列里面去。会按照cache key来分类。
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
        new HashMap<String, Queue<Request<?>>>();

    /**
     * 提交上来的请求,(包括了mWaitingRequests,mCacheQueue,mNetworkQueue里面所有的请求)。
     */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

    /**
     * 缓存请求队列,受CacheDispatcher的调度。
     */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /**
     * 网络请求队列,受NetworkDispatcher的调度。
     */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

    /**
     * Volley默认会开四个NetworkDispatcher,从队列(mNetworkQueue)里面取出数据,
     * 交给BasicNetwork处理,去网络上面获取数据信息。
     */
    private NetworkDispatcher[] mDispatchers;

    /**
     * 从队列(mCacheQueue)里面取出数据,根据情况交给DiskBasedCache去处理或者加入网络请求队列。
     */
    private CacheDispatcher mCacheDispatcher;
2. RequestQueue主要函数介绍(注释直接在代码里面给出哦)
    /**
     * RequestQueue提供了三个构造函数,cache:缓存的实现提供了一些缓存的规则,network:网络请求类做的事情就是去网络请求数据的,
     * threadPoolSize:开几个线程去处理网络请求, delivery:把请求到的结果post到指定的线程当中去(一般是post到UI线程当中去)
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery)
    public RequestQueue(Cache cache, Network network, int threadPoolSize)
    public RequestQueue(Cache cache, Network network)

    /**
     * 告诉CacheDispatcher,NetworkDispatcher该准备干活了。可以开始调度了,从各种的队列里面循环的去取网络请求,做相应的处理。
     */
    public void start()

    /**
     * 告诉CacheDispatcher,NetworkDispatcher该停掉了
     */
    public void stop()

    /**
     * 累加,用来设置Request.setSequence()。
     */
    public int getSequenceNumber()

    /**
     * 获取缓存对象
     */
    public Cache getCache()

    /**
     * 取消指定的请求(RequestFilter用来指定刷选条件)
     */
    public void cancelAll(RequestFilter filter)
    public void cancelAll(final Object tag)

    /**
     * 添加一个请求到请求队列里面去(可能是缓存队列,也可能是网络请求队列这个就要看情况了)
     */
    public <T> Request<T> add(Request<T> request)

    /**
     * 某个请求结束了会调用这个函数
     */
    <T> void finish(Request<T> request)

    /**
     * 添加和删除请求完成时候的监听
     */
    public  <T> void addRequestFinishedListener(RequestFinishedListener<T> listener)
    public  <T> void removeRequestFinishedListener(RequestFinishedListener<T> listener)

add()函数的详细解释,每次进行一个新的网络请求都会调用这个函数。

    /**
     * 当要发起一个网络请求的时候,会调用这个函数。函数的参数就是我们的请求对象Request。把Request添加到请求队列里面去。
     */
    public <T> Request<T> add(Request<T> request) {
        // 把RequestQueue设置给Request。这样当Request请求结束的时候可以调用到RequestQueue里面的finish()函数,做相应的处理。
        request.setRequestQueue(this);
        // 把Request添加到mCurrentRequests当中去。mCurrentRequests里面记录了所有的Request,方便查找做处理。
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // 给Request设置序号。(在优先级相同的情况下,用他来判断哪个先出队)
        request.setSequence(getSequenceNumber());
        // 调试信息时用到
        request.addMarker("add-to-queue");

        // 如果Request不允许缓存,直接把Request添加到网络请求队列里面
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        // 接下来处理都是在Request允许缓存的基础上进行的。

        // mWaitingRequests是一个map对象Map<String, Queue<Request<?>>>,这个map的key值是request.getCacheKey(),value对应的是一个Queue队列
        // request.getCacheKey()相同的请求都会放到这个Queue队列里面。
        synchronized (mWaitingRequests) {
            // 拿到请求对应的cache key。(默认情况下是url)
            String cacheKey = request.getCacheKey();
            // 相同的cache key是否已经添加过。
            if (mWaitingRequests.containsKey(cacheKey)) {
                // 如果之前已经添加了这个cache key。把这个请求放到mWaitingRequests指定key的value(Queue队列)里面去,这里你可能会想为什么没有添加到
                // 缓存队列里面去呢,不添加到缓存队列里面去怎么会处理到这个请求呢。在finish()函数里面会做你想的事情。
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // 有相同的cache key的请求,加入到mWaitingRequests当中去。注意哦这里map的value是null。并没有把请求加入进去哦。
                mWaitingRequests.put(cacheKey, null);
                // 把请求添加到缓存队列里面去。过后CacheDispatcher会调度处理这个请求。
                mCacheQueue.add(request);
            }
            return request;
        }
    }

finish()函数详细解释,在每个请求结束的时候都会自动调用这个函数。

    /**
     * 当每个请求处理完之后都会调用这个函数
     */
    <T> void finish(Request<T> request) {
        // 从mCurrentRequests中把对应的request移除掉。
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        // 回调RequestFinishedListener里面的函数
        synchronized (mFinishedListeners) {
            for (RequestFinishedListener<T> listener : mFinishedListeners) {
                listener.onRequestFinished(request);
            }
        }
        // 如果这个请求是支持缓存的,
        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                // 拿到请求对应的cache key。(默认情况下是url)
                String cacheKey = request.getCacheKey();
                // 从mWaitingRequests中把cache key对应的Queue<Request<?>>全部拿出来。(Queue<Request<?>>里面的请求都是还没有加入到缓存队列里面去的)
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                    waitingRequests.size(), cacheKey);
                    }
                    // 把Queue<Request<?>>所有的请求添加到缓存队列里面去,让CacheDispatcher可以调度到这些请求
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }
3. 总结RequestQueue做的事情
  • 启动CacheDispatcher线程,CacheDispatcher主要和缓存队列mCacheQueue还有缓存(DiskBasedCache)打交道。CacheDispatcher会循环的从mCacheQueue队列里面取出请求任务然后去缓存里面取结果数据,如果取到的数据无效,则把请求任务又重新添加到网络请求队列里面,取到的数据有效,则是把结果反馈到上层来处理。
  • 启动NetworkDispatcher线程,Volley框架默认启动四个NetworkDispatcher去处理网络请求队列mNetworkQueue里面的请求,通过BasicNetwork(HttpClientStack或者HurlStack)去网络获取数据,然后把结果反馈上来。
  • 添加请求任务。【RequestQueue类的add()函数】
  • 取消请求任务。【RequestQueue类的cancelAll()函数】
  • 处理每个请求任务结束的扫尾工作。【RequestQueue类的finish()函数,每个请求任务结束的时候都会调用到这个函数】
CacheDispatcher,NetworkDispatcher类(请求任务的调度)

       这部分的内容就是去缓存请求队列,网络请求队列里面取出请求任务,然后指导去获取请求任务对应的数据(可能是从缓存获取,也可能从网络获取)。

1. CacheDispatcher类

       CacheDispatcher是一个线程,CacheDispatcher会在RequestQueue里面启动。CacheDispatcher主要四个数据打交道

  • 缓存请求队列(RequestQueue里面的mCacheQueue),CacheDispatcher会一直从缓存请求队列里面取出请求任务,去缓存里面获取数据。
  • 缓存DiskBasedCache(只是从缓存去获取数据),在从缓存队列里面拿到了请求任务的时候先尝试去缓存里面获取对应的数据。
  • 网络请求队列(RequestQueue里面的mNetworkQueue),当请求任务在缓存中不能正确获取数据的时候,该请求任务会被添加网络请求队列。
  • ExecutorDelivery,用来post请求任务对应的结果的,因为请求任务都是在后台线程处理的,在任务结束的时候我们要把结果放到前台线程UI线程里面去处理。

       CacheDispatcher里面最重要的就是run()函数了,对run()简单的注解如下

    @Override
    public void run() {
        // 调试信息用的
        if (DEBUG) VolleyLog.v("start new dispatcher");
        // 设置当前CacheDispatcher线程的优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // 初始化缓存(DiskBasedCache类的initialize函数,去缓存文件里面把信息解析到Map<String,CacheHeader> mEntries里面去)
        mCache.initialize();

        // 循环
        while (true) {
            try {
                // 从缓存请求队列mCacheQueue取出一个请求任务,因为缓存队列是PriorityBlockingQueue<Request<?>>形式,在出队的时候是有优先级的
                final Request<?> request = mCacheQueue.take();
                // 调试的时候看打印的
                request.addMarker("cache-queue-take");

                // 如果请求任务取消了,这个任务就抛弃了,不用继续了,直接执行下一个任务。
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 从缓存DiskBasedCache里面拿出缓存信息(cache key默认是url)
                Cache.Entry entry = mCache.get(request.getCacheKey());
                // 如果没有缓存信息,把这个请求任务添加到网络请求队列里面去mNetworkQueue,让他从网络获取数据
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }

                // 如果缓存时间过期了,同样把这个请求任务添加到网络请求队列里面去
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 拿到了请求任务对应的数据了
                request.addMarker("cache-hit");
                // 把结果信息封装到Response对象里面去。
                Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                // 如果请求任务对应的数据不需要刷新,直接把结果通过ExecutorDelivery post出去。(默认是post到UI线程中去)
                if (!entry.refreshNeeded()) {
                    mDelivery.postResponse(request, response);
                } else {
                    // 如果请求任务对应的数据需要刷新
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // 标记请求任务的结果已经post过了。
                    response.intermediate = true;

                    // 先把请求任务对应的结果通过ExecutorDelivery post出去,然后在把请求任务添加到网络请求队列里面去
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
2. NetworkDispatcher类

       NetworkDispatcher也是一个线程,NetworkDispatcher也会在RequestQueue里面启动,NetworkDispatcher主要四个数据打交道。

  • 网络请求队列(RequestQueue里面的mNetworkQueue),NetworkDispatcher会一直从网络请求队列里面取出请求任务,去网络上面获取数据。
  • 网络请求BasicNetwork(可能关联的是HttpClientStack或者HurlStack),每个网络请求任务都是通过BasicNetwork的performRequest()函数去网络上面获取数据的。
  • 缓存DiskBasedCache(只是存入缓存),因为如果某个请求需要缓存,在正确拿到数据之后要把相应的数据缓存起来的。
  • ExecutorDelivery,用来post请求任务对应的结果的,因为请求任务都是在后台线程处理的,在任务结束的时候我们要把结果放到前台线程UI线程里面去处理。

       NetworkDispatcher里面最重要的也就是run()函数了,对run()简单的注解如下

    @Override
    public void run() {
        // 设置NetworkDispatcher的优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            // 获取当前时间
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            // 从网络队列里面取出请求任务
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // 判断请求任务是否被取消了,取消了就不处理了,去拿下一个请求任务了
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }
                // 设置线程的标识
                addTrafficStatsTag(request);

                // 通过BasicNetwork的performRequest()函数去网络上面获取数据
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // 如果这条请求任务的数据没有改变,并且这个请求任务的结果已经post过了(请求任务对应的结果之前在CacheDispatcher已经post过了)
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 把结果封装到Response对象里面去
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 如果请求任务需要缓存,把结果缓存起来
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 把请求任务对应的结果post出去
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 请求有异常post异常结果
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                // 请求有异常post异常结果
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }
DiskBasedCache,BasicNetwork类(数据获取层)

       Volley框架是用来请求数据的,不管是从缓存中获取数据还是从网络中获取数据。最后都的把数据获取到。DiskBasedCache和BasicNetwork就是做这个工作的。

1. DiskBasedCache类

       DiskBasedCache实现了Cache,做的主要的工作就是把缓存信息存到文件当中,和从文件中正确的读取到缓存信息。Cache的内部类Entry就是我们的缓存的信息。缓存的具体做法是先会把Entry分成两部分来缓存一部分是CacheHeader,另一部分就是具体的data数据了。然后把这两部分的数据存放到以cache key对应的文件里面。
       DiskBasedCache主要函数介绍

    // 初始化缓存
    @Override
    public synchronized void initialize() {
        // 文件缓存的目录不存在,创建。
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }

        // 获取缓存目录下面所有的文件
        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }
        // 遍历缓存目录下面所有的文件
        for (File file : files) {
            BufferedInputStream fis = null;
            try {
                // 读取文件信息
                fis = new BufferedInputStream(new FileInputStream(file));
                // 把文件信息解析到CacheHeader对象entry里面
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();
                // 把CacheHeader信息存放mEntries当中去,方便操作有必要的时候不用每次都去读文件
                putEntry(entry.key, entry);
            } catch (IOException e) {
                if (file != null) {
                    file.delete();
                }
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ignored) { }
            }
        }
    }

    // 获取指定key的缓存信息
    @Override
    public synchronized Cache.Entry get(String key) {
        // 判断key之前是否保存过
        CacheHeader entry = mEntries.get(key);
        // if the entry does not exist, return.
        if (entry == null) {
            return null;
        }

        // 获取缓存文件
        File file = getFileForKey(key);
        CountingInputStream cis = null;
        try {
            // 读到文件信息
            cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
            // 去掉缓存头部信息
            CacheHeader.readHeader(cis); // eat header
            // 获取到缓存的data信息(这个是请求任务对应的实实在在的数据)
            byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
            // 把信息转换成Cache.Entry然后返回
            return entry.toCacheEntry(data);
        } catch (IOException e) {
            VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
            remove(key);
            return null;
        } finally {
            if (cis != null) {
                try {
                    cis.close();
                } catch (IOException ioe) {
                    return null;
                }
            }
        }
    }

    // 保存缓存信息
    @Override
    public synchronized void put(String key, Cache.Entry entry) {
        // 判断缓存空间是否够用
        pruneIfNeeded(entry.data.length);
        // 拿到缓存文件
        File file = getFileForKey(key);
        try {
            // 准备把缓存信息写入到缓存当中去
            BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
            // 把部分缓存信息转换为CacheHeader缓存头信息
            CacheHeader e = new CacheHeader(key, entry);
            // CacheHeader缓存头信息写入缓存文件
            boolean success = e.writeHeader(fos);
            if (!success) {
                fos.close();
                VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
                throw new IOException();
            }
            // 实实在在的缓存信息写入缓存文件
            fos.write(entry.data);
            fos.close();
            // 把CacheHeader信息存放mEntries当中去,方便操作有必要的时候不用每次都去读文件
            putEntry(key, e);
            return;
        } catch (IOException e) {
        }
        boolean deleted = file.delete();
        if (!deleted) {
            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
        }
    }
2. BasicNetwork类

       BasicNetwork的作用是指导请求任务和网络打交道,去网络上面获取数据。获取网络数据最终的体现在HttpClientStack或者HurlStack上。BasicNetwork只公布了而一个方法出来performRequest(),在这个方法里面会调用HttpClientStack或者HurlStack的performRequest()方法。调用网络请求的一些api去网络上面获取数据,还有一点可能比较终于的就是在请求失败的情况下的重试的功能了对应Request里面提到的getRetryPolicy()。

ExecutorDelivery类(把请求任务的结果传递出来)

       ExecutorDelivery实现了ResponseDelivery接口,对于一个请求任务,不管是去缓存中获取数据还是去网络上面获取数据,都是在后台线程中处理,在获取到结果之后可能要把结果post传递到UI线程中来。

    /**
     * 传递请求任务正确的请求结果,request:请求任务,response:请求任务对应的结果
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * 传递请求任务正确的请求结果,在结果传递完之后会执行runnable里的内容
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 传递请求任务错误的请求结果
     */
    public void postError(Request<?> request, VolleyError error);

四. Volley框架ImageLoader简单分析

       Volley加载图片的步骤

  • 创建RequestQueue对象,和前面讲的一样一般都是在Application中做,尽量一个应用一个RequestQueue对象。
  • 实现ImageCache接口的getBitmap()和putBitmap()方法。(一般借助LruCache内存缓存实现)
  • 创建ImageLoader对象。
  • 获取ImageListener对象,这里会把ImageView传进去,不管图片获取成功还是失败都会回调ImageListener里面的方法。
  • 确定我们要显示的图片的大小。(为了减少内存的占用)
  • 调用ImageLoader的get()方法加载网络上的图片。

       Volley框架中和加载图片打交道的主要是ImageLoader和ImageRequest类。

ImageRequest类(图片请求任务)

       ImageRequest类,专门用来对付图片的请求。

1. ImageRequest类中所的事情
  • 封装图片网络请求的一些信息(一般图片请求就一个url没啥别的参数)。

  • 在正确获取都网络请求数据之后把网络上面拿到的数据转换为Bitmap,当然转换的过程中也是很有讲究的(因为涉及图片,内存就比较大了,要特别小心的处理)
           1). 如果需要的图片的高度和宽度都没有设定,那么直接把网络获取的数据原封不动的解析成Bitmap。
           2). 如果需要的图片的高度或者宽度有设定,则是先从网络信息里面解析到实际图片的大小信息。然后按照ScaleType调整图片所需要的大小,算出decodeOptions.inJustDecodeBounds的值,然后再解析到Bitmap。

  • 把请求的结果传递出来,不管是正确的结果还是有异常的结果都传递出来。

2. ImageRequest类中的几个函数做一个简单的介绍。
    /**
     * 构造函数两个,关注一个就好了
     * @param url:网络请求图片url地址。
     * @param listener:图片请求成功的监听回调(对象是Bitmap)
     * @param maxWidth:需要图片的宽度
     * @param maxHeight:需要图片的高度
     * @param scaleType:控制图片如何resized/moved来匹对ImageView的size
     * @param decodeConfig:图片的配置信息(经常把配置弄的比较低点减少内存的占用)
     * @param errorListener:图片请求失败的监听回调
     */
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
                        ImageView.ScaleType scaleType, Bitmap.Config decodeConfig, Response.ErrorListener errorListener)


    /**
     * 调整图所需图片的大小(下面函数中每行的解释,就以宽度为主导说明maxPrimary:宽度, maxSecondary:高度, actualPrimary:原始宽度,
     * actualSecondary:原始高度)
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                                           int actualSecondary, ImageView.ScaleType scaleType) {

        // 如果没有指定我们所需图片的宽度和高度,返回原始图片的宽度
        if ((maxPrimary == 0) && (maxSecondary == 0)) {
            return actualPrimary;
        }

        // 如果scaleType的模式是ScaleType.FIT_XY那么以指定的宽度为准,如果没有指定宽度以图片的原始宽度为准
        if (scaleType == ImageView.ScaleType.FIT_XY) {
            if (maxPrimary == 0) {
                return actualPrimary;
            }
            return maxPrimary;
        }

        // 如果宽度没有指定,会根据指定的高度去等比例缩放
        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;

        // 如果scaleType的模式是ScaleType.CENTER_CROP(将原图的中心对准ImageView的中心,等比例放大原图)
        // 举个里面应该更好理解 调整前需要的图片大小比如50*50,原始图片大小为200*100,宽度高度调整后图片大小为50*25了
        if (scaleType == ImageView.ScaleType.CENTER_CROP) {
            if ((resized * ratio) < maxSecondary) {
                resized = (int) (maxSecondary / ratio);
            }
            return resized;
        }

        if ((resized * ratio) > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // 保证同一时刻只有一个解析的任务在进行
        synchronized (sDecodeLock) {
            try {
                // 调用doParse()函数,把从网络获取的数据转换为Bitmap
                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));
            }
        }
    }

    /**
     * The real guts of parseNetworkResponse. Broken out for readability.
     */
    private Response<Bitmap> doParse(NetworkResponse response) {
        // 网络上面拿到的图片字节信息
        byte[] data = response.data;
        // 转Bitmap的配置信息
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            // 没有指定需要图片的高度和宽度,按照指定的decodeOptions.inPreferredConfig直接解析出原始图片
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // 设置只是去解析图片的大小
            decodeOptions.inJustDecodeBounds = true;
            // 解析图片的大小信息
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            // 得到原始图片的宽度和高度
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // 调整宽度,得到调整后的宽度
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                                                   actualWidth, actualHeight, mScaleType);
            // 调整高度,得到调整后的高度
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                                                    actualHeight, actualWidth, mScaleType);

            // 设置去解析Bitmap
            decodeOptions.inJustDecodeBounds = false;
            // 根据所需要的大小和图片原始的大小计算得到decodeOptions.inSampleSize,
            // findBestSampleSize函数的计算方式是通用的只要是去计算inSampleSize都可以用这个函数。
            decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            // 解析到Bitmap,现在解析到的Bitmap的大小就是我们指定的大小了,并不是原始的图片的大小,大大的减少了内存的消耗
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // 如果有必要的话在对Bitmap做缩放处理。进一步的减少内存
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                                       tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                                                   desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        // 转换成Response<Bitmap>的对象
        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }
ImageLoader类(Volley获取图片的入口)
1. ImageLoader类中的几个函数做一个简单的介绍。
    /**
     * ImageLoader构造函数两个参数
     * @param queue:请求队列
     * @param imageCache:缓存的实现,这里完全抛给我们自己去处理,我们只需要实现ImageCache接口里getBitmap和putBitmap函数就可以了
     */
    public ImageLoader(RequestQueue queue, ImageCache imageCache)

    /**
     * ImageLoader里面默认给我们实现的ImageListener接口onErrorResponse()函数获取数据有异常的时候调用,onResponse()获取数据无异常的时候调用
     * @param view:我们获取到的Bitmap的要显示的控件
     * @param defaultImageResId:在没有获取到Bitmap之前默认要显示的图片id
     * @param errorImageResId:在获取Bitmap失败的情况下要显示的图片的id
     * @return 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);
                }
            }
        };
    }

    /**
     * 准备去网络上面获取Bitmap信息,按我们正常的逻辑肯定是先生成一个Request<Bitmap>对象然后在加入到请求队列里面去。正好这个函数就处理这些事情的
     * @param requestUrl:图片信息的url
     * @param imageListener:图片获取过程中的监听(成功或者失败回调)
     * @param maxWidth:需要图片的宽度
     * @param maxHeight:需要图片的高度
     * @param scaleType:需要图片的ScaleType,用于进一步控制图片的大小
     * @return 返回Bitmap mBitmap,ImageListener mListener,String mCacheKey,String mRequestUrl组合起来的一个对象。也是好理解的比如我们
     *
     */
    public ImageContainer get(String requestUrl, ImageListener imageListener,
                              int maxWidth, int maxHeight, ImageView.ScaleType scaleType) {

        // 判断是不是在UI线程
        throwIfNotOnMainThread();
        // 获取缓存的key字符串
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // 去缓存中获取Bitmap
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 获取到了缓存数据
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            // 回调ImageListener监听的onResponse()在ImageView上显示图片
            imageListener.onResponse(container, true);
            return container;
        }

        // 缓存中没有数据
        // 生存一个ImageContainer对象,这个时候ImageContainer对象的Bitmap是null,因为还没有拿到吗
        ImageContainer imageContainer =
            new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // 在去网络上面获取Bitmap之前,显示默认的图片
        imageListener.onResponse(imageContainer, true);

        // 判断是否有相同的任务在进行
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // 有相同的任务在进行
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // 没有相同的任务在进行,声明一个Request<Bitmap>对象添加到缓存队列里面去,让NetworkDispatcher调度去网络获取Bitmap数据
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                                                      cacheKey);

        mRequestQueue.add(newRequest);
        // 记录下
        mInFlightRequests.put(cacheKey,
                              new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

    /**
     * 生成Request<Bitmap>请求对象
     */
    protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
                                               ImageView.ScaleType scaleType, final String cacheKey) {
        return new ImageRequest(requestUrl, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                // 获取Bitmap成功
                onGetImageSuccess(cacheKey, response);
            }
        }, maxWidth, maxHeight, scaleType, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 获取Bitmap异常
                onGetImageError(cacheKey, error);
            }
        });
    }

    /**
     * 请求任务Bitmap获取成功调用
     */
    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 保存缓存
        mCache.putBitmap(cacheKey, response);

        // 从mInFlightRequests移出BatchedImageRequest
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            // 更新Bitmap
            request.mResponseBitmap = response;

            // 批量处理请求结果信息
            batchResponse(cacheKey, request);
        }
    }

    /**
     * 请求任务Bitmap获取异常
     */
    protected void onGetImageError(String cacheKey, VolleyError error) {
        // 从mInFlightRequests移出BatchedImageRequest
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            // 设置错误信息
            request.setError(error);

            // 批量处理请求结果信息
            batchResponse(cacheKey, request);
        }
    }

    /**
     * 批量处理Bitmap请求结果信息(这个函数默认也是在UI线程执行的)
     */
    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        // 防止漏掉,怕延时的过程中有新的BatchedImageRequest添加进来
        mBatchedResponses.put(cacheKey, request);
        //
        if (mRunnable == null) {
            mRunnable = new Runnable() {
                @Override
                public void run() {
                    // 循环所有的mBatchedResponses
                    for (BatchedImageRequest bir : mBatchedResponses.values()) {
                        // 循环所有的ImageContainer,刚才get()函数0-0行有提到相同的任务请求不用多次添加到请求队列里面,正好这里就处理好了
                        for (ImageContainer container : bir.mContainers) {
                            // 如果当前Bitmap请求没有设置ImageListener
                            if (container.mListener == null) {
                                continue;
                            }
                            if (bir.getError() == null) {
                                // 调用Bitmap请求对应的ImageListener的onResponse()方法
                                container.mBitmap = bir.mResponseBitmap;
                                container.mListener.onResponse(container, false);
                            } else {
                                // 调用Bitmap请求对应的ImageListener的onErrorResponse()方法
                                container.mListener.onErrorResponse(bir.getError());
                            }
                        }
                    }
                    mBatchedResponses.clear();
                    mRunnable = null;
                }

            };
            // Post到UI线程中去执行
            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
        }
    }

五. Volley框架提供的NetworkImageView类(继承自ImageView)

       NetworkImageView类是Volley提供专门用来显示网络图片的ImageView控件。

       NetworkImageView类中主要函数介绍

    /**
     * 根据网络图片的url,去设置ImageView
     */
    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // 调用loadImageIfNecessary()函数,那就得专门来看下这个函数了。
        loadImageIfNecessary(false);
    }

    void loadImageIfNecessary(final boolean isInLayoutPass) {
        // 获取控件的宽度和高度
        int width = getWidth();
        int height = getHeight();
        // 获取设置的ImageView.ScaleType类型
        ScaleType scaleType = getScaleType();

        boolean wrapWidth = false, wrapHeight = false;
        // 宽度和高度是否使用内容的大小
        if (getLayoutParams() != null) {
            wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
            wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
        }

        // 不知道控件的大小。直接return掉,
        boolean isFullyWrapContent = wrapWidth && wrapHeight;
        if (width == 0 && height == 0 && !isFullyWrapContent) {
            return;
        }

        // 图片的url是否为空
        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;
            } else {
                // 不同的请求,则把之前的请求取消掉
                mImageContainer.cancelRequest();
                setDefaultImageOrNull();
            }
        }

        // 如果大小是使用内容的大小,那边把限制大小设置为0(换句话说就是不限制大小),否则世界使用空间的大小。
        int maxWidth = wrapWidth ? 0 : width;
        int maxHeight = wrapHeight ? 0 : height;

        // 开始ImageLoader.get()去获取图片了
        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, scaleType);

        // update the ImageContainer to be the new bitmap container.
        mImageContainer = newContainer;
    }

       Volley的简单分析结束了,能够感觉到Volley框架的优美,Volley框架非常强的扩展性。最后还想提个疑问,对于Volley框架缓存处理这块,用url作为cache key不能确保唯一性,比如有这么个情况,请求方式是Post形式,好几个请求的url都是一样的,不一样的只是body提供的参数不一样,这个时候这好几个请求的缓存会不会乱掉,因为用url是区分不开的。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
你可以使用 Android Volley 库来下载文件。下面是一个简单的示例代码: ```java String fileUrl = "http://www.example.com/file.pdf"; String fileName = "file.pdf"; StringRequest downloadRequest = new StringRequest(Request.Method.GET, fileUrl, new Response.Listener<String>() { @Override public void onResponse(String response) { // 文件下载完成,执行相应操作 } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // 文件下载出错,处理错误情况 } }) { @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<>(); // 如果需要,可以在这里设置请求头 return headers; } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { // 在这里获取文件名 String contentDisposition = response.headers.get("Content-Disposition"); if (contentDisposition != null) { String[] parts = contentDisposition.split(";"); for (String part : parts) { if (part.trim().startsWith("filename")) { fileName = part.split("=")[1].trim(); break; } } } return super.parseNetworkResponse(response); } }; // 添加请求请求队列 Volley.newRequestQueue(context).add(downloadRequest); ``` 上述代码中,你需要替换 `fileUrl` 为要下载的文件的 URL,`fileName` 为要保存的文件名。你可以在 `onResponse` 方法中处理文件下载完成后的操作,而在 `onErrorResponse` 方法中处理下载出错的情况。如果需要设置请求头,可以在 `getHeaders` 方法中添加相应的请求头。在 `parseNetworkResponse` 方法中,你可以根据需要从响应的头信息中获取文件名。 请确保在使用 Volley 之前已经在你的项目中添加了 Volley 的依赖。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值