天天看點

Volley Manager 不能發送Header的Bug

項目中使用了Volley Manger這個庫,是對Volley又封裝了一層,實作了一個通用的ByteArrayRequest,可以用來發送各種類型的請求,也可以上傳檔案。

隻有很少的9個類,卻大大簡化了Volley的使用。

但是在與伺服器端保持Session的時候,出現了問題,Session一直保持不了。

檢視運作日志,擷取SessionID和發送SessionID時是都正确,就是多次登入伺服器會傳回多個不同的SessionID,于是懷疑sessionid根本就沒有發送給伺服器端。Volley是個通用的網絡請求庫,問題應該不大,于是懷疑,Volley Manager在使用Volley時可能不當,導緻header沒有發送出去。

抓包驗證了上述結論,自己填充的Cookie的header确實沒有發送出去。

之前讀過Volley的一部分源碼,問題定位到Volley Manager自定義的ByteArrayRequest#getHeaders()

其實作如下:

@Override
  public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> headers = super.getHeaders();
    if (null == headers || headers.equals(Collections.emptyMap())) {
      headers = new HashMap<String, String>();
    }
    return headers;
  }      

檢視super.getHeaders()的實作:

public Map<String, String> getHeaders() throws AuthFailureError {
<span style="white-space:pre">  </span>return Collections.emptyMap();
}      

問題出現了,當Volley架構調用xxxRequest的getHeaders方法時,肯定是個空的HashMap。

于是修改ByteArrayRequest類的相關部分實作。

增加下面的域定義:

private Map<String,String> mHeaders = new HashMap<String, String>();      

修改getHeaders方法如下:

public Map<String, String> getHeaders() throws AuthFailureError {
<span style="white-space:pre">  </span>return this.mHeaders;}      

為什麼修改XXXRequest的getHeaders方法可以?

原因如下:

Volley裡面的RequestQueue是Volley的最最核心的類,RequestQueue可以将Volley所有的類串起來。

其内部保持了一個NetworkDispatcher的數組,NetworkDispatcher繼承自Thread類,是一個線程,用于排程處理走網絡的請求。啟動後會不斷從網絡請求隊列中取請求處理,隊列為空則等待,請求處理結束則将結果傳遞給ResponseDelivery去執行後續處理,并判斷結果是否要進行緩存。他跟加到RequestQueue中的Request對象一一對應。

NetworkDispatcher的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對象      
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");
                    continue;
                }

                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");
                    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);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } 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);
            }
        }
    }      

具體看上邊的注解就可以了。最最核心的一行代碼牽扯到一個Network對象。要看Network的作用,可以看一下Volley提供的Network的基本實作BasicNetwork類。

BasicNetwork#performRequest的實作如下:

@Override
public NetworkResponse performRequest(Request<?> request)
      throws VolleyError {
   long requestStart = SystemClock.elapsedRealtime();
   while (true) {
      HttpResponse httpResponse = null;
      byte[] responseContents = null;
      Map<String, String> responseHeaders = new HashMap<String, String>();
      try {
         // Gather headers.
         Map<String, String> headers = new HashMap<String, String>();
         addCacheHeaders(headers, request.getCacheEntry());

         httpResponse = mHttpStack.performRequest(request, headers);
         responseHeaders.putAll(httpResponse.getAllHeaders());
         int statusCode = httpResponse.getResponseCode();
         // Handle cache validation.
         if (statusCode == HttpResponse.SC_NOT_MODIFIED) {
            Entry entry = request.getCacheEntry();
            if (entry == null) {
               return new NetworkResponse(
                     HttpResponse.SC_NOT_MODIFIED, null,
                     responseHeaders, true,
                     SystemClock.elapsedRealtime() - requestStart);
            }

            // A HTTP 304 response does not have all header fields. We
            // have to use the header fields from the cache entry plus
            // the new ones from the response.
            // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
            entry.responseHeaders.putAll(responseHeaders);
            return new NetworkResponse(HttpResponse.SC_NOT_MODIFIED,
                  entry.data, entry.responseHeaders, true,
                  SystemClock.elapsedRealtime() - requestStart);
         }

         // Handle moved resources
         if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
               || statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
            String newUrl = responseHeaders.get("Location");
            request.setRedirectUrl(newUrl);
         }

         // Some responses such as 204s do not have content. We must
         // check.
         if (httpResponse.getEntity() != null) {
            responseContents = entityToBytes(httpResponse.getEntity());
         } 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,
               httpResponse);

         if (statusCode < 200 || statusCode > 299) {
            throw new IOException();
         }
         return new NetworkResponse(statusCode, responseContents,
               responseHeaders, false, SystemClock.elapsedRealtime()
                     - requestStart);
      } catch (SocketTimeoutException e) {
         attemptRetryOnException("socket", request, new TimeoutError());
      } catch (MalformedURLException e) {
         throw new RuntimeException("Bad URL " + request.getUrl(), e);
      } catch (IOException e) {
         int statusCode = 0;
         NetworkResponse networkResponse = null;
         if (httpResponse != null) {
            statusCode = httpResponse.getResponseCode();
         } else {
            throw new NoConnectionError(e);
         }
         if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
               || statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
            VolleyLog.e("Request at %s has been redirected to %s",
                  request.getOriginUrl(), request.getUrl());
         } else {
            VolleyLog.e("Unexpected response code %d for %s",
                  statusCode, request.getUrl());
         }
         if (responseContents != null) {
            networkResponse = new NetworkResponse(statusCode,
                  responseContents, responseHeaders, false,
                  SystemClock.elapsedRealtime() - requestStart);
            if (statusCode == HttpResponse.SC_UNAUTHORIZED
                  || statusCode == HttpResponse.SC_FORBIDDEN) {
               attemptRetryOnException("auth", request,
                     new AuthFailureError(networkResponse));
            } else if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
                  || statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
               attemptRetryOnException("redirect", request,
                     new AuthFailureError(networkResponse));
            } else {
               // TODO: Only throw ServerError for 5xx status codes.
               throw new ServerError(networkResponse);
            }
         } else {
            throw new NetworkError(networkResponse);
         }
      }
   }
}      

可以看出,真正執行請求的不是BasicNetwork而是調用了HttpStack的performRequest方法。BasicNetwork隻是在請求之前處理了一下請求頭,請求響應之後,處理一下響應的結果。将請求結果轉換為可以被ResponseDelivery處理的NetworkResponse對象。

下面看一下HttpStack,HttpStack是一個接口,Volley提供了一個java.net.HttpUrlConnection的實作HurlStack。

下面看一下HurlStack#performRequest方法的實作:

@Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>()
        //重點在這裡
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        HttpResponse response = new HttpResponse(connection.getResponseCode(), connection.getResponseMessage());
        response.setEntity(entityFromConnection(connection));
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                response.addHeader(header.getKey(), header.getValue().get(0));
            }
        }
        return response;
    }      
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);