Volley源码解析

作者: 砺雪凝霜 | 来源:发表于2018-01-13 08:54 被阅读838次

    Volley的优缺点

    优点

    • 自动的调度网络请求
    • 多并发的网络请求
    • 可以缓存http请求
    • 支持请求的优先级
    • 支持取消请求的API,可以取消单个请求,可以设置取消请求的范围域。
    • 代码标准化,使开发者更容易专注于我们的业务的逻辑处理
    • 更容易给UI填充来自网络请求的数据
    • Volley可以是作为调试和跟踪的工具(用起来特别爽~)

    缺点

    • 使用的是httpclient、HttpURLConnection
    • 6.0不支持httpclient了,如果想支持得添加org.apache.http.legacy.jar
    • 非常不适合大的文件流操作,例如上传和下载。因为Volley会把所有的服务器端返回的数据在解析期间缓存进内存。
    • 只支持http请求
    • 图片加载性能一般

    Volley的工作原理

    Volley工作流程.png

    源码解析
    下面开始Volley源码解析,为了方便理解,我们按照Volley的实际开发使用流程来分析其源码。
    (1)初始化RequestQueue

    • 按照Volley的使用方法,我们最先开始要初始化一个RequestQueue,由于新建一个RequestQueue非常消耗资源,开发的时候只需创建一次即可
       RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
    
    • 接着会执行Volley这个类的newRequestQueue方法
      public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
            BasicNetwork network;
            if (stack == null) {
                if (Build.VERSION.SDK_INT >= 9) {
                    network = new BasicNetwork(new HurlStack());
                } else {
                    String userAgent = "volley/0";
                    try {
                        String packageName = context.getPackageName();
                        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                        userAgent = packageName + "/" + info.versionCode;
                    } catch (NameNotFoundException e) {
                    }
    
                    network = new BasicNetwork(
                            new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
                }
            } else {
                network = new BasicNetwork(stack);
            }
    
            return newRequestQueue(context, network);
        }
    

    上面源码可以看出,如果Android版本大于或等于2.3,则调用给予HttpURLCollection的HurlStack,否则就调用HttpClient的HttpClientStack,接下来Volley会创建RequestQueue,并调用它的start方法。

       public void start() {
            stop();  // Make sure any currently running dispatchers are stopped.
            // Create the cache dispatcher and start it.
            mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
            mCacheDispatcher.start();
    
            // Create network dispatchers (and corresponding threads) up to the pool size.
            for (int i = 0; i < mDispatchers.length; i++) {
                NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                        mCache, mDelivery);
                mDispatchers[i] = networkDispatcher;
                networkDispatcher.start();
            }
        }
    

    RequestQueue对象创建的时候,会初始化一个缓存调度线程(CacheDispatcher),和4个网络调度线程(NetworkDispatcher),所以Volley默认会在后台开启5个线程。线程都初始化之后,分别调用其start方法开启线程。
    (2) 把Request请求添加进RequestQueue请求队列之后的流程

    queue.add(stringRequest);
    
    • 把 Request请求添加进RequestQueue中,首先通过执行request.shouldCache()方法判断request是否应该缓存,默认情况下是true,也就是所有的请求默认都要缓存。如果request不需要缓存的话,把请求放进网络请求队列,如果request需要缓存那就放进缓存队列。
     public <T> Request<T> add(Request<T> request) {
            // Tag the request as belonging to this queue and add it to the set of current requests.
            request.setRequestQueue(this);
            synchronized (mCurrentRequests) {
                mCurrentRequests.add(request);
            }
    
            // Process requests in the order they are added.
            request.setSequence(getSequenceNumber());
            request.addMarker("add-to-queue");
    
            // If the request is uncacheable, skip the cache queue and go straight to the network.
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
            mCacheQueue.add(request);
            return request;
         }
    

    ** RequestQueue的add方法并没有进行网络或对缓存的操作,当请求添加金网络请求队列或者缓存队列时,在后台的网络调度线程和缓存调度线程会轮询各自的请求队列,如果发现请求任务需要处理则开始执行。下面分别来分析CacheDispatcher和NetworkDispatcher的源码。**

    (3)CacheDispatcher的工作流程

    • CacheDispatcher线程的run方法里是一个死循环,并不断地执行processRequest方法
      @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 {
                processRequest();
      } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
      if (mQuit) {
                    return;
      }
            }
        }
    }
    
    • CacheDispatcher线程的processRequest方法
        private void processRequest() throws InterruptedException {
            // 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");
                return;
            }
    
            // 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);
                }
                return;
            }
    
            // 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);
                }
                return;
            }
    
            // 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);
                }
            }
        }
    
    

    可以发现该方法如果请求被取消的话,退出该方法;如果请求没有被取消则判断请求是否有缓存的响应。如果有缓存的响应并且没有过期,则对缓存响应进行解析并回调给主线程;如果没有缓存的响应,则将请求加入网络队列,接下来看看网络调度线程NetworkDispatcher是如何工作的。

    (4)NetworkDispatcher的工作流程

    • NetworkDispatcher的run方法同样也会执行processRequest方法,如下所示:
       @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
                try {
                    processRequest();
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                }
            }
        }
    

    processRequest方法如下所示:

     private void processRequest() throws InterruptedException {
            // Take a request from the queue.
            Request<?> request = mQueue.take();
    
            long startTimeMs = SystemClock.elapsedRealtime();
            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();
                    return;
                }
    
                addTrafficStatsTag(request);
    
                // Perform the network request.
                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();
                    return;
                }
    
                // 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();
            }
        }
    

    网络调度线程也是不断地从队列中取出请求并且判断该请求是否被取消了。如果该请求没有被取消,就去请求网络,并把网络请求的数据回调回主线程。请求网络时调用Network的performRequest()方法,下面看看Network的这个类的performRequest方法。

      @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());
                    httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                    int statusCode = httpResponse.getStatusCode();
    
                    responseHeaders = httpResponse.getHeaders();
                    // Handle cache validation.
                    if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                        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.
                        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());
                    }
                }
            }
        }
    

    可以发现通过mBaseHttpStack的executeRequest方法返回响应的数据,其实mBaseHttpStack就是前面提的HulStack和HttpClientStack的父类。回到NetworkDispatcher请求网络后,会将响应结果存在缓存中,并调用下面这段代码Delivery的postResponse方法。如下所示

    mDelivery.postResponse(request, response);
    
       @Override
        public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
            request.markDelivered();
            request.addMarker("post-response");
            mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
        }
    

    ResponseDeliveryRunnable里面做了什么:

       @SuppressWarnings("unchecked")
            @Override
            public void run() {
                // NOTE: If cancel() is called off the thread that we're currently running in (by
                // default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
                // won't be called, since it may be canceled after we check isCanceled() but before we
                // deliver the response. Apps concerned about this guarantee must either call cancel()
                // from the same thread or implement their own guarantee about not invoking their
                // listener after cancel() has been called.
    
                // If this request has canceled, finish it and don't deliver.
                if (mRequest.isCanceled()) {
                    mRequest.finish("canceled-at-delivery");
                    return;
                }
    
                // Deliver a normal response or error, depending.
                if (mResponse.isSuccess()) {
                    mRequest.deliverResponse(mResponse.result);
                } else {
                    mRequest.deliverError(mResponse.error);
                }
    
                // If this is an intermediate response, add a marker, otherwise we're done
                // and the request can be finished.
                if (mResponse.intermediate) {
                    mRequest.addMarker("intermediate-response");
                } else {
                    mRequest.finish("done");
                }
    
                // If we have been provided a post-delivery runnable, run it.
                if (mRunnable != null) {
                    mRunnable.run();
                }
           }
    

    上面代码可以看到如果响应成功将会执行Request的deliverResponse方法,并把响应结果传进去,如果响应失败,就执行deliverError方法,并把响应失败的对象传进去。接着我们就看看Request的deliverResponse都干了什么。Request的子类有很多,这里就拿StringRequest来做参考。

      @Override
        protected void deliverResponse(String response) {
            Response.Listener<String> listener;
            synchronized (mLock) {
                listener = mListener;
            }
            if (listener != null) {
                listener.onResponse(response);
            }
        }
    

    错误的回调在父类Request中可以找到

       public void deliverError(VolleyError error) {
            Response.ErrorListener listener;
            synchronized (mLock) {
                listener = mErrorListener;
            }
            if (listener != null) {
                listener.onErrorResponse(error);
            }
        }
    

    拿到响应结果之后,如果请求成功则回调onResponse
    ,如果请求失败则回调onErrorResponse,整个流程就是这样了。


    源码中涉及到的一些知识点

    (1)Volley是如何把请求的数据回调回主线程中的?

    使用Handler.postRunnable(Runnable)方法回调回主线程中进行处理,ExecutorDelivery的构造方法中可以看到这段代码,如下所示:

      public ExecutorDelivery(final Handler handler) {
            // Make an Executor that just wraps the handler.
            mResponsePoster = new Executor() {
                @Override
                public void execute(Runnable command) {
                    handler.post(command);
                }
            };
        }
    

    (2)Volley开启了几个后台线程?

    总共开启了5个线程:1个缓存调度线程和4个网络调度线程,并且线程的优先级为10,即后台线程。Volley其实并没有开启线程池去维护线程,而是硬性地开了5个线程,这点我觉得是可以进行优化的。

     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    

    (3)Volley进行网络请求时用到了Http协议的哪些字段?
    响应的数据解析在HttpHeaderParser类中的parseCacheHeaders方法中可以找到

      public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
            long now = System.currentTimeMillis();
    
            Map<String, String> headers = response.headers;
    
            long serverDate = 0;
            long lastModified = 0;
            long serverExpires = 0;
            long softExpire = 0;
            long finalExpire = 0;
            long maxAge = 0;
            long staleWhileRevalidate = 0;
            boolean hasCacheControl = false;
            boolean mustRevalidate = false;
    
            String serverEtag = null;
            String headerValue;
    
            headerValue = headers.get("Date");
            if (headerValue != null) {
                serverDate = parseDateAsEpoch(headerValue);
            }
    
            headerValue = headers.get("Cache-Control");
            if (headerValue != null) {
                hasCacheControl = true;
                String[] tokens = headerValue.split(",");
                for (int i = 0; i < tokens.length; i++) {
                    String token = tokens[i].trim();
                    if (token.equals("no-cache") || token.equals("no-store")) {
                        return null;
                    } else if (token.startsWith("max-age=")) {
                        try {
                            maxAge = Long.parseLong(token.substring(8));
                        } catch (Exception e) {
                        }
                    } else if (token.startsWith("stale-while-revalidate=")) {
                        try {
                            staleWhileRevalidate = Long.parseLong(token.substring(23));
                        } catch (Exception e) {
                        }
                    } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                        mustRevalidate = true;
                    }
                }
            }
    
            headerValue = headers.get("Expires");
            if (headerValue != null) {
                serverExpires = parseDateAsEpoch(headerValue);
            }
    
            headerValue = headers.get("Last-Modified");
            if (headerValue != null) {
                lastModified = parseDateAsEpoch(headerValue);
            }
    
            serverEtag = headers.get("ETag");
    
            // Cache-Control takes precedence over an Expires header, even if both exist and Expires
            // is more restrictive.
            if (hasCacheControl) {
                softExpire = now + maxAge * 1000;
                finalExpire = mustRevalidate
                        ? softExpire
                        : softExpire + staleWhileRevalidate * 1000;
            } else if (serverDate > 0 && serverExpires >= serverDate) {
                // Default semantic for Expire header in HTTP specification is softExpire.
                softExpire = now + (serverExpires - serverDate);
                finalExpire = softExpire;
            }
    
            Cache.Entry entry = new Cache.Entry();
            entry.data = response.data;
            entry.etag = serverEtag;
            entry.softTtl = softExpire;
            entry.ttl = finalExpire;
            entry.serverDate = serverDate;
            entry.lastModified = lastModified;
            entry.responseHeaders = headers;
            entry.allResponseHeaders = response.allHeaders;
    
            return entry;
        }
    

    用到的Http协议的字段如下:

    • Date:返回服务器时间,如果想得到服务器的时候,我们可以从这里获取

    • Cache-Control:为no-cache和no-store:不缓存响应数据,如果需要缓存响应数据,当需要设置缓存时,通过maxAge的值来设置缓存过期的时间。

    • Must-revalidate和proxy-revalidate:该值为一个boolean值,服务器告诉客户端,缓存数据过期前,可以使用缓存;缓存一旦过期,必须去源服务器进行有效性校验。

    • Expires:设置缓存过期的时间,如果 Cache-Control设置为需要缓存,那么优先以 Cache-Control的maxAge的值来设置缓存过期时间。

    • Last-Modified:在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。
      客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过,如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。它和请求头的if-modified-since字段去判断资源有没有被修改的。

    • ETags:它和if-None-Match(HTTP协议规格说明定义ETag为“被请求变量的实体值”,或者是一个可以与Web资源关联的记号)常用来判断当前请求资源是否改变。类似于Last-Modified和HTTP-IF-MODIFIED-SINCE。但是有所不同的是Last-Modified和HTTP-IF-MODIFIED-SINCE只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性,不如资源的MD5等。

    关于Volley的其它用法,可以参照我之前写的几篇文章
    Volley的基本使用方法
    Volley的封装
    那些年我使用Volley遇到的坑

    好的就这些了,如果对你有用记得点个赞表示小小的鼓励一下。

    相关文章

      网友评论

        本文标题:Volley源码解析

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