1. 程式人生 > >徹底掌握網路通訊(八)AsyncHttpClient原始碼解讀

徹底掌握網路通訊(八)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的分析到此結束 - _ -