項目中使用了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);