2017-10-9(Volley使用范例源码分析)

作者: 721d739b6619 | 来源:发表于2017-10-10 10:52 被阅读18次

Volley应该是比较久远的产物了。google在2013 IO发布,但也可以借鉴学习毕竟是google工程师的AOSP产物。
下面从范例代码分析Volley的结构和核心源码。

//创建RequestQueue 队列
RequestQueue mQueue = Volley.newRequestQueue(context);
        //url
        String url = "http//www.baidu.com";
       //返回结果处理
        Response.Listener listener =  new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.e("TAG",response);
            }
        };
//错误返回结果处理
        Response.ErrorListener errorListener = new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("TAG",error.getMessage(),error);
            }
        };
//Request请求
StringRequest stringRequest = new StringRequest(Request.Method.GET,url,
               listener, errorListener);
//将请求加入RequestQueue 队列
mQueue.add(stringRequest);

实际就三个步骤:

  • 创建队列 RequestQueue
  • 创建Request请求
  • 将Request请求添加到队列RequestQueue

创建RequestQueue

RequestQueue mQueue = Volley.newRequestQueue(context);

Paste_Image.png

调用两个参数的构造方法:

Paste_Image.png Paste_Image.png

这个方法有点长截取重要部分
主要是对于9以上版本是创建BasicNetwork对象
然后调用newRequestQueue(context, network);
看看BasicNetwork对象干什么的?

Paste_Image.png

创建了一个4*1024大小的二进制数组和将new HurlStack()传进来。
好,看到这里先把这个类放着,后边再继续看到底做了些什么。

将context和BasicNetwork传进来

newRequestQueue(context, network);

Paste_Image.png

第一句代码就不说了,就是在设备上创建一个文件放缓存;

主要看后面两句:

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();

Paste_Image.png Paste_Image.png

这里看见创建了一个ExecutorDelivery,和我们十分熟悉的Handler,而这个Handler是主线程的Handler;看看这个ExecutorDelivery是干什么的:

Paste_Image.png

看到这里,ExecutorDelivery主要就是创建一个主线程的Handler,将返回的信息给主线程的Handler处理。Runnable 就是给主线程处理的。
继续看刚刚的构造方法

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

Paste_Image.png
  • cache 就是DiskBasedCache主要就是缓存
  • network 就是BasicNetwork主要就是发送http请求的,但真正实现发送是HurlStack,后面会带大家看源码讲解。
  • mDispatchers就是创建一个NetworkDispatcher数组
  • mDelivery 刚才已经看了代码就是给主线程Handler处理回调结果用的

RequestQueue创建完毕下一步

queue.start()

Paste_Image.png

stop();英文注解写得很清楚就是确保目前正在执行的dispatcher停止(不管是网络的还是缓存的)

Paste_Image.png

之后的代码逐行解释

mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);

  • mCacheQueue是缓存队列
  • mNetworkQueue是网络请求队列
  • mCache刚才说了是DiskBasedCache主要就是缓存
  • mDelivery刚才已经看了代码就是给主线程Handler处理回调结果用的
Paste_Image.png

这里多了个WaitingRequestManager,看看干什么的:

Paste_Image.png

先放着,构造函数并没有干什么

mCacheDispatcher.start();
我们看看CacheDispatcher类

Paste_Image.png

继承Tread,那么start()即是调用run()

@Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        //最高优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                        mNetworkQueue.put(request);
                    }
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                        mNetworkQueue.put(request);
                    }
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    //发送请求
                    mDelivery.postResponse(request, response);
                } else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    // Mark the response as intermediate.
                    response.intermediate = true;

                    if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                        // Post the intermediate response back to the user and have
                        // the delivery then forward the request along to the network.
                        mDelivery.postResponse(request, response, new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    mNetworkQueue.put(request);
                                } catch (InterruptedException e) {
                                    // Restore the interrupted status
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
                    } else {
                        // request has been added to list of waiting requests
                        // to receive the network response from the first request once it returns.
                        mDelivery.postResponse(request, response);
                    }
                }

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

方法比较长,我说重点:
首先设置了该线程为最高级别,然后缓存初始化,之后就是一个无限循环。里面首先在缓存队列拿出一个request,如果该request是已经取消就退出循环。不是继续往下走,从缓存mCache取一个entry,如果是空的话,mWaitingRequestManager.maybeAddToWaitingRequests(request),调用这句。看看干什么的:

private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
            String cacheKey = request.getCacheKey();
            // Insert request into stage if there's already a request with the same cache key
            // in flight.
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new ArrayList<Request<?>>();
                }
                request.addMarker("waiting-for-response");
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
                return true;
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                request.setNetworkRequestCompleteListener(this);
                if (VolleyLog.DEBUG) {
                    VolleyLog.d("new request, sending to network %s", cacheKey);
                }
                return false;
            }
        }
Paste_Image.png

由于我们是第一次作请求,基本就没有缓存,所以是走else部分:

Paste_Image.png

里面只将cacheKey给mWaitingRequests存了起来,然后就是我截图红色框部分,最后返回false;
setNetworkRequestCompleteListener实现接口,其实就是这个:

Paste_Image.png

继续看回之前的:

Paste_Image.png

因为返回false,即调用mNetworkQueue.put(request);
网络队列添加了一个request,然后跳出循环;

继续看回RequestQueue的start():

Paste_Image.png

刚才走到红色箭头,继续往下走:
由于mDispathcers的容量设置了4个,所以循环4次:
里面就是new一个NetworkDispatcher

Paste_Image.png
  • mNetworkQueue是网络队列
  • network 就是BasicNetwork主要就是发送http请求的,但真正实现发送是HurlStack
  • cache 就是DiskBasedCache主要就是缓存
  • mDelivery 刚才已经看了代码就是给主线程Handler处理回调结果用的

networkDispatcher.start();
看看NetworkDispatcher类:

Paste_Image.png

和CacheDispatcher类一样,就不多说,直接看run()

 @Override
    public void run() {
        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 the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    request.notifyListenerResponseNotUsable();
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                //执行http请求mNetwork 是BasicNetwork
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    request.notifyListenerResponseNotUsable();
                    continue;
                }

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response);
                request.notifyListenerResponseReceived(response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
                request.notifyListenerResponseNotUsable();
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
                request.notifyListenerResponseNotUsable();
            }
        }
    }

NetworkDispatcher这个类和CacheDispatcher很相似,不同之处只是CacheDispatcher是通过缓存获得返回结果,而NetworkDispatcher是通过发送网络请求获得。

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

第一步也是设置该线程为最高级别;然后就是一个无限循环;

request = mQueue.take();

就是从网络集合获取request对象;

Paste_Image.png

这句代码与CacheDispatcher不同,看到注解啦吧;

这里mNetwork是什么呢?其实就是

Paste_Image.png

BasicNetwork,看看它的performRequest()方法:

/**
     * 执行Request请求
     * */
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        //返回一个开机以来包括睡眠的度过时间
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                // Gather headers.
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                //返回http结果
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                //状态码
                int statusCode = httpResponse.getStatusCode();

                //response头部
                responseHeaders = httpResponse.getHeaders();
                // Handle cache validation.
                /**
                 * 当出现304(服务端有缓存且有效)会自己构建一个Response返回
                 * */
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    /**
                     * 304:
                     * Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档
                     * 返回 304 的时候已经做了一次数据库查询,但是可以避免接下来更多的数据库查询,
                     * 并且没有返回页面内容而只是一个 HTTP Header,
                     * 从而大大的降低带宽的消耗,对于用户的感觉也是提高。
                     * 服务器会自动完成 Last Modified(缓存文件的) 和 If Modified Since(请求中包含的)
                     * */
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true,
                                SystemClock.elapsedRealtime() - requestStart, responseHeaders);
                    }
                    // Combine cached and response headers so the response will be complete.
                    //entry为Response的body
                    List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                    return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
                            true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);
                }

                // Some responses such as 204s do not have content.  We must check.
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                  responseContents =
                          inputStreamToBytes(inputStream, httpResponse.getContentLength());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                //记录请求时间
                logSlowRequests(requestLifetime, request, responseContents, statusCode);

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, false,
                        SystemClock.elapsedRealtime() - requestStart, responseHeaders);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents, false,
                            SystemClock.elapsedRealtime() - requestStart, responseHeaders);
                    if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED ||
                            statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // Don't retry other client errors.
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                            attemptRetryOnException("server",
                                    request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

很长一大段,我们看核心的:

Paste_Image.png

一开始想通过request获取缓存entry通过entry获取http头部信息;
然后

httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);

mBaseHttpStack又是什么玩意呢?
其实就是之前

Paste_Image.png

所以上边我已经透露过真正执行http请求的是HurlStack

这里我不过多截图了,有兴趣可以看看Volley源码HurlStack的executeRequest方法。其实这里它是通过HttpURLConnection来完成http请求的。相信大家对HttpURLConnection都很熟悉,在没有使用过任何网络请求框架的时候Android不是使用Apache的httpclient就是使用java里面的HttpURLConnection。

我们继续BasicNetwork的performRequest:

Paste_Image.png

请求返回获取状态码,获取返回的头部信息
通过状态码判断如果是304的话:
说明客户端有缓冲的文档,文档信息还是有效的。

如果不是304状态码,

Paste_Image.png

获取了返回的内容
最后返回NetworkResponse对象

Paste_Image.png

继续看回NetworkDispatcher:
获取到返回的NetworkResponse

Paste_Image.png

request.parseNetworkResponse(networkResponse);
这句代码只是将返回的内容通过头部编码信息转换一下;

其实这里还有一段代码就是将返回的信息作缓存,下次如果有同一个请求的时候如果状态码返回304就可以通过缓存获取不走网络请求内容了。

核心代码是

mDelivery.postResponse(request, response);
这段代码其实就是ExcutorDelivery的postResponse

Paste_Image.png

下面截图是ResponseDeliveryRunnable的run方法

Paste_Image.png

就是将返回结果给Request的deliverResponse或deliverError

Paste_Image.png

这里只截了deliverResponse,看看里面其实就是我们范例代码的listener。

整个Volley的请求流程就是这样一个流程了。现在总结一下:

  • Volley实际是通过HttpURLConnection完成网络请求的;
  • Volley最大的特点是有缓存队列,先走缓存队列,缓存队列没有信息再走网络队列;
  • Volley没有创建线程池,而是默认创建4个Thread执行网络请求;
  • 最后Volley使用了PriorityBlockingQueue这个队列,该队列特点线程安全的可以根据优先级别获取元素,而存放在PriorityBlockingQueue里面的元素需要实现Comparable接口;
Paste_Image.png Paste_Image.png

最后放一张自己画的VolleyUML图,宏观理解一下Volley的结构:

VolleyUML.png

由于只是从范例代码的角度看源码,有些类没有涉及到看,如果有什么问题欢迎指正和学习,共同交流。

相关文章

网友评论

    本文标题:2017-10-9(Volley使用范例源码分析)

    本文链接:https://www.haomeiwen.com/subject/qkwpyxtx.html