徹底掌握網路通訊(八)AsyncHttpClient原始碼解讀
網路通訊系列文章序
在之前的文章中,我們系統的分析了httpclient的內部發送過程,以及httpclient是如何重連,保活等機制;這章我們就來看下以httpclient為原型而封裝的一個非同步請求傳送庫AsyncHttpClient,下載地址
1:一次完整發送過程
可見AsyncHttpClient的傳送也是依賴DefaultHttpClient類的execute方法,總結一下具體的步驟
1.1)建立AsyncHttpClient例項,在這個過程中主要完成一些httpparams引數的設定,執行緒池的建立,重發機制的建立等等
1.2)呼叫post或者get等方法,傳入實現了ResponseHandlerInterface介面的類,該介面主要用於回撥請求成功和失敗
1.3)在呼叫post和get之後,會構建AsyncHttpRequest例項,該例項實現了runnable介面,並將該例項提交到執行緒池
1.4)提交到執行緒池之後,AsyncHttpRequest的makeRequest方法會呼叫DefaultHttpClient的excute方法完成http請求,之後呼叫ResponseHandlerInterface的sendResponseMessage方法
1.5)在ResponseHandlerInterface具體實現類的sendResponseMessage方法中會完成資源的釋放,並通過handler將處理結果(如成功,失敗的)訊息傳送出去
2:我們看下AsyncHttpClient中的DefaultHttpClient和Apach中的DefaultHttpClient在實現上有什麼區別
2.1)AsyncHttpClient中的DefaultHttpClient的類圖
2.2)Apach中的DefaultHttpClient的類圖
從上面的兩個類圖可見,他們兩者之間的區別就在於AbstractHttpClient的實現上,AsyncHttpClient繼承了CloseableHttpClient類,同時CloseableHttpClient實現了Closeable介面和HttpClient介面,這樣做的好處就是AsyncHttpClient更容易對資源進行釋放和處理
3:AsyncHttpClient的常用發起請求的程式碼
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.baidu.com", new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int i, cz.msebera.android.httpclient.Header[] headers, byte[] bytes) {
Log.d("hwj" , "**AsyncHttpClientActivity onSuccess**");
}
@Override
public void onFailure(int i, cz.msebera.android.httpclient.Header[] headers, byte[] bytes, Throwable throwable) {
Log.d("hwj", "**AsyncHttpClientActivity onFailure**");
}
});
很簡答的程式碼,下面我們就這個post請求做下原始碼分析
4:原始碼分析
4.1)例項化AsyncHttpClient
public AsyncHttpClient() {
this(false, 80, 443);
}
呼叫
public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
this(getDefaultSchemeRegistry(fixNoHttpResponseException, httpPort, httpsPort));
}
最終呼叫如下方法完成例項的建立
public AsyncHttpClient(SchemeRegistry schemeRegistry) {
this.maxConnections = 10;
this.connectTimeout = 10000;
this.responseTimeout = 10000;
this.isUrlEncodingEnabled = true;
BasicHttpParams httpParams = new BasicHttpParams();
ConnManagerParams.setTimeout(httpParams, (long)this.connectTimeout);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(this.maxConnections));
ConnManagerParams.setMaxTotalConnections(httpParams, 10);
HttpConnectionParams.setSoTimeout(httpParams, this.responseTimeout);
HttpConnectionParams.setConnectionTimeout(httpParams, this.connectTimeout);
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
ClientConnectionManager cm = this.createConnectionManager(schemeRegistry, httpParams);
Utils.asserts(cm != null, "Custom implementation of #createConnectionManager(SchemeRegistry, BasicHttpParams) returned null");
this.threadPool = this.getDefaultThreadPool();
this.requestMap = Collections.synchronizedMap(new WeakHashMap());
this.clientHeaderMap = new HashMap();
this.httpContext = new SyncBasicHttpContext(new BasicHttpContext());
this.httpClient = new DefaultHttpClient(cm, httpParams);
this.httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) {
if (!request.containsHeader("Accept-Encoding")) {
request.addHeader("Accept-Encoding", "gzip");
}
String header;
for(Iterator var3 = AsyncHttpClient.this.clientHeaderMap.keySet().iterator(); var3.hasNext(); request.addHeader(header, (String)AsyncHttpClient.this.clientHeaderMap.get(header))) {
header = (String)var3.next();
if (request.containsHeader(header)) {
Header overwritten = request.getFirstHeader(header);
AsyncHttpClient.log.d("AsyncHttpClient", String.format("Headers were overwritten! (%s | %s) overwrites (%s | %s)", header, AsyncHttpClient.this.clientHeaderMap.get(header), overwritten.getName(), overwritten.getValue()));
request.removeHeader(overwritten);
}
}
}
});
this.httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
public void process(HttpResponse response, HttpContext context) {
HttpEntity entity = response.getEntity();
if (entity != null) {
Header encoding = entity.getContentEncoding();
if (encoding != null) {
HeaderElement[] var5 = encoding.getElements();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
HeaderElement element = var5[var7];
if (element.getName().equalsIgnoreCase("gzip")) {
response.setEntity(new AsyncHttpClient.InflatingEntity(entity));
break;
}
}
}
}
}
});
this.httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState)context.getAttribute("http.auth.target-scope");
CredentialsProvider credsProvider = (CredentialsProvider)context.getAttribute("http.auth.credentials-provider");
HttpHost targetHost = (HttpHost)context.getAttribute("http.target_host");
if (authState.getAuthScheme() == null) {
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
if (creds != null) {
authState.setAuthScheme(new BasicScheme());
authState.setCredentials(creds);
}
}
}
}, 0);
this.httpClient.setHttpRequestRetryHandler(new RetryHandler(5, 1500));
}
第6~14行,設定httpparams引數
第15行,設定客戶端連線管理,在Apache的DefaultHttpClient中,客戶端連線管理預設為SingleClientConnManager,管理客戶端連線管理者的作用,可參考這裡管理客戶端連線管理者
第17行,設定執行緒池,用於將http請求提交到執行緒池中
第21行,設定DefaultHttpClient的例項
第22行~76行,設定Http請求的攔截器
第77行,設定http請求的重試機制
4.1.1)看下執行緒池
protected ExecutorService getDefaultThreadPool() {
return Executors.newCachedThreadPool();
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通過原始碼我們可以看見建立了一個max_value,佇列為SynchronousQueue的執行緒池
4.2)提交http請求到執行緒池中
在構建好AsyncHttpClient例項之後,就可以呼叫post方法完成請求的傳送
public RequestHandle post(String url, ResponseHandlerInterface responseHandler) {
return this.post((Context)null, url, (RequestParams)null, responseHandler);
}
其最終呼叫
public RequestHandle post(Context context, String url, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) {
return this.sendRequest(this.httpClient, this.httpContext, this.addEntityToRequestBase(new HttpPost(this.getURI(url)), entity), contentType, responseHandler, context);
}
這個方法裡面,有一個HttpEntity entity的引數,當你的http請求中,如果有需要新增到訊息體裡的內容,就可以通過這個引數進行新增
我們看下sendRequest方法
protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) {
if (uriRequest == null) {
throw new IllegalArgumentException("HttpUriRequest must not be null");
} else if (responseHandler == null) {
throw new IllegalArgumentException("ResponseHandler must not be null");
} else if (responseHandler.getUseSynchronousMode() && !responseHandler.getUsePoolThread()) {
throw new IllegalArgumentException("Synchronous ResponseHandler used in AsyncHttpClient. You should create your response handler in a looper thread or use SyncHttpClient instead.");
} else {
if (contentType != null) {
if (uriRequest instanceof HttpEntityEnclosingRequestBase && ((HttpEntityEnclosingRequestBase)uriRequest).getEntity() != null && uriRequest.containsHeader("Content-Type")) {
log.w("AsyncHttpClient", "Passed contentType will be ignored because HttpEntity sets content type");
} else {
uriRequest.setHeader("Content-Type", contentType);
}
}
responseHandler.setRequestHeaders(uriRequest.getAllHeaders());
responseHandler.setRequestURI(uriRequest.getURI());
AsyncHttpRequest request = this.newAsyncHttpRequest(client, httpContext, uriRequest, contentType, responseHandler, context);
this.threadPool.submit(request);
RequestHandle requestHandle = new RequestHandle(request);
if (context != null) {
Map var10 = this.requestMap;
List requestList;
synchronized(this.requestMap) {
requestList = (List)this.requestMap.get(context);
if (requestList == null) {
requestList = Collections.synchronizedList(new LinkedList());
this.requestMap.put(context, requestList);
}
}
requestList.add(requestHandle);
Iterator iterator = requestList.iterator();
while(iterator.hasNext()) {
if (((RequestHandle)iterator.next()).shouldBeGarbageCollected()) {
iterator.remove();
}
}
}
return requestHandle;
}
}
第19行,建立AsyncHttpRequest request例項,AsyncHttpRequest 實現了runnable介面
第20行,將上面建立的request提交到執行緒池中去進行http請求的傳送
第21行,建立RequestHandle requestHandle
第22行~33行,將requestHandle新增到requestList 的list中,這樣開發者就可以呼叫cancelRequests來取消一個正在進行http請求了
第34行~41行,如果是已經執行的http請求,則需要在requestList 清空掉
4.3:傳送http請求
當我們把AsyncHttpRequest request提交到執行緒池中,一個http請求就會被髮起,看下AsyncHttpRequest 的run方法
public void run() {
if (!this.isCancelled()) {
if (!this.isRequestPreProcessed) {
this.isRequestPreProcessed = true;
this.onPreProcessRequest(this);
}
if (!this.isCancelled()) {
this.responseHandler.sendStartMessage();
if (!this.isCancelled()) {
try {
this.makeRequestWithRetries();
} catch (IOException var2) {
if (!this.isCancelled()) {
this.responseHandler.sendFailureMessage(0, (Header[])null, (byte[])null, var2);
} else {
AsyncHttpClient.log.e("AsyncHttpRequest", "makeRequestWithRetries returned error", var2);
}
}
if (!this.isCancelled()) {
this.responseHandler.sendFinishMessage();
if (!this.isCancelled()) {
this.onPostProcessRequest(this);
this.isFinished = true;
}
}
}
}
}
}
第8行,當這個請求沒有被cancle的情況下,即沒有呼叫cancelRequests的情況下,方將請求發出去
第9行,通過handler傳送一個what為2的message給AsyncHttpResponseHandler的handleMessage處理,AsyncHttpResponseHandler在接收到what為2的msg回撥onStart方法,onStart是空方法體,可自行實現其邏輯
第12行,呼叫makeRequestWithRetries將請求真正傳送出去
第15行,當http請求在經過重試並出現IOException 異常的時候,通過handler傳送一個what為1的message給AsyncHttpResponseHandler的handleMessage處理,AsyncHttpResponseHandler在接收到what為1的msg回撥onFailure方法
第21行~26行,當一個請求被正常執行,通過handler傳送一個what為3的message給AsyncHttpResponseHandler的handleMessage處理,AsyncHttpResponseHandler在接收到what為3的msg回撥onFinish方法,onFinish是空方法體,可自行實現其邏輯
我們重點看下makeRequestWithRetries是如何傳送http請求的
private void makeRequestWithRetries() throws IOException {
boolean retry = true;
IOException cause = null;
HttpRequestRetryHandler retryHandler = this.client.getHttpRequestRetryHandler();
while(true) {
try {
if (retry) {
try {
this.makeRequest();
return;
} catch (UnknownHostException var5) {
cause = new IOException("UnknownHostException exception: " + var5.getMessage());
retry = this.executionCount > 0 && retryHandler.retryRequest(var5, ++this.executionCount, this.context);
} catch (NullPointerException var6) {
cause = new IOException("NPE in HttpClient: " + var6.getMessage());
retry = retryHandler.retryRequest(cause, ++this.executionCount, this.context);
} catch (IOException var7) {
if (this.isCancelled()) {
return;
}
cause = var7;
retry = retryHandler.retryRequest(var7, ++this.executionCount, this.context);
}
if (retry) {
this.responseHandler.sendRetryMessage(this.executionCount);
}
continue;
}
} catch (Exception var8) {
AsyncHttpClient.log.e("AsyncHttpRequest", "Unhandled exception origin cause", var8);
cause = new IOException("Unhandled exception: " + var8.getMessage());
}
throw cause;
}
}
第4行,得到我們在建立AsyncHttpClient例項的時候設定的HttpRequestRetryHandler
第6行,開啟while迴圈,當出現異常的情況下,我們可以通過HttpRequestRetryHandler來嘗試重新發送http請求
第10行,呼叫 makeRequest完成http訊息傳送
private void makeRequest() throws IOException {
if (!this.isCancelled()) {
if (this.request.getURI().getScheme() == null) {
throw new MalformedURLException("No valid URI scheme was provided");
} else {
if (this.responseHandler instanceof RangeFileAsyncHttpResponseHandler) {
((RangeFileAsyncHttpResponseHandler)this.responseHandler).updateRequestHeaders(this.request);
}
HttpResponse response = this.client.execute(this.request, this.context);
if (!this.isCancelled()) {
this.responseHandler.onPreProcessResponse(this.responseHandler, response);
if (!this.isCancelled()) {
this.responseHandler.sendResponseMessage(response);
if (!this.isCancelled()) {
this.responseHandler.onPostProcessResponse(this.responseHandler, response);
}
}
}
}
}
}
第10行,通過呼叫DefaultHttpClient的execute方法完成http請求的傳送和接收,這個過程和Apache的DefaultHttpClient的執行過程是一致的,有不明白的地方可以參考
徹底掌握網路通訊(四)Android原始碼中HttpClient的傳送框架解析
徹底掌握網路通訊(五)DefaultRequestDirector解析
第14行,呼叫AsyncHttpResponseHandler的sendResponseMessage,將response傳遞給AsyncHttpResponseHandler
我們在看下AsyncHttpResponseHandler的sendResponseMessage方法
public void sendResponseMessage(HttpResponse response) throws IOException {
if (!Thread.currentThread().isInterrupted()) {
StatusLine status = response.getStatusLine();
byte[] responseBody = this.getResponseData(response.getEntity());
if (!Thread.currentThread().isInterrupted()) {
if (status.getStatusCode() >= 300) {
this.sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()));
} else {
this.sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody);
}
}
}
}
第4行,將response轉為byte[]
第9行,通過handler傳送一個what為0的message給AsyncHttpResponseHandler的handleMessage處理,AsyncHttpResponseHandler在接收到what為0的msg回撥onSuccess方法
我們注意看下第4行的getResponseData的方法,這裡有很關鍵的資源釋放動作
byte[] getResponseData(HttpEntity entity) throws IOException {
byte[] responseBody = null;
if (entity != null) {
InputStream instream = entity.getContent();
if (instream != null) {
long contentLength = entity.getContentLength();
if (contentLength > 2147483647L) {
throw new IllegalArgumentException("HTTP entity too large to be buffered in memory");
}
int buffersize = contentLength <= 0L ? 4096 : (int)contentLength;
try {
ByteArrayBuffer buffer = new ByteArrayBuffer(buffersize);
try {
byte[] tmp = new byte[4096];
long count = 0L;
int l;
while((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) {
count += (long)l;
buffer.append(tmp, 0, l);
this.sendProgressMessage(count, contentLength <= 0L ? 1L : contentLength);
}
} finally {
AsyncHttpClient.silentCloseInputStream(instream);
AsyncHttpClient.endEntityViaReflection(entity);
}
responseBody = buffer.toByteArray();
} catch (OutOfMemoryError var16) {
System.gc();
throw new IOException("File too large to fit into available memory");
}
}
}
return responseBody;
}
從第26行開始,就是預設處理一些資源的回收動作
AsyncHttpClient的分析到此結束 - _ -