在Android開發中,發送、處理http請求簡直太常見了,以至於我們的代碼里到處充斥着各種HttpClient和與之相關又臭又長的代碼,
它們存在於你代碼的各個角落,每次看見都令人作嘔,而你僅僅是為了server能返回一個string或者json給你。每次當我自己寫這樣
的代碼時,我都會想能不能簡化下這個流程,可能2、3行代碼就能搞定。因為針對最簡單的case,我只需要提供request url,成功時的
callback和(或)失敗時的callback,僅此而已。針對這一類問題(需求),可以說android-async-http提供了幾乎完美的解決方案。
通過使用它可以大大簡化你的代碼,不僅如此,你的代碼看上去也優雅多了。
當我第一眼看到它時就被吸引住了,特別是async關鍵字,干我們這行的都知道,這是異步執行,也就是說它的網絡請求自動在非UI
線程里執行,你不需要任何額外的操作(比如手動new一個Thread之類)。項目的官方網站:
http://loopj.com/android-async-http/,對應的github地址:https://github.com/loopj/android-async-http
我這里簡要介紹下:它是專門針對Android在Apache的HttpClient基礎上構建的異步的callback-based http client。所有的請求
全在UI線程之外發生,而callback發生在創建它的線程中,應用了Android的Handler發送消息機制。你也可以把AsyncHttpClient應用在
Service中或者后台線程中,庫代碼會自動識別出它所運行的context。它的feature包括:
1. 發送異步http請求,在匿名callback對象中處理response;
2. http請求發生在UI線程之外;
3. 內部采用線程池來處理並發請求;
4. GET/POST 參數構造,通過RequestParams類。
5. 內置多部分文件上傳,不需要第三方庫支持;
6. 流式Json上傳,不需要額外的庫;
7. 能處理環行和相對重定向;
8. 和你的app大小相比來說,庫的size很小,所有的一切只有90kb;
9. 自動智能的請求重試機制在各種各樣的移動連接環境中;
10. 自動的gzip響應解碼;
11. 內置多種形式的響應解析,有原生的字節流,string,json對象,甚至可以將response寫到文件中;
12. 永久的cookie保存,內部實現用的是Android的SharedPreferences;
13. 通過BaseJsonHttpResponseHandler和各種json庫集成;
14. 支持SAX解析器;
15. 支持各種語言和content編碼,不僅僅是UTF-8。
大概翻譯了下,這些只是大體的概覽,具體的細節還得在使用過程中慢慢感受、學習。
接下來,帶領大家看看應用android-async-http來寫代碼是個啥樣子。簡單來說你只需要3步,
1. 創建一個AsyncHttpClient;
2. (可選的)通過RequestParams對象設置請求參數;
3. 調用AsyncHttpClient的某個get方法,傳遞你需要的(成功和失敗時)callback接口實現,一般都是匿名內部類
,實現了AsyncHttpResponseHandler,類庫自己也提供了好些現成的response handler,你一般不需要自己創建一個。
來看看代碼如何寫:
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {
@Override
public void onStart() {
// called before request is started
}
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] response) {
// called when response HTTP status is "200 OK"
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
// called when response HTTP status is "4XX" (eg. 401, 403, 404)
}
@Override
public void onRetry(int retryNo) {
// called when request is retried
}
});
是不是很簡潔,有沒有被震撼到?反正我自己第一次看到的時候有種相見恨晚的感覺,這簡直就是我日思夜想的方式啊!這里你只需要通過
匿名內部類的方式實現AsyncHttpResponseHandler,而且更棒的是你只需要override感興趣的方法,比如一般都是onSuccess和onFailure。
這個版本的get方法沒有為請求傳遞任何參數,當然你也可以通過RequestParams來傳遞各種參數,如下:
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.put("key", "value");
params.put("more", "data");
client.get("http://www.google.com", params, new
AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] response) {
System.out.println(response);
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
Log.d("ERROR", error);
}
}
);
以上的例子是返回的response直接是原生字節流的情況,如果你需要把返回的結果當一個String對待,這時只需要匿名實現一個
TextHttpResponseHandler就行,其繼承自AsyncHttpResponse,並將原生的字節流根據指定的encoding轉化成了string對象,
代碼如下:
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.put("key", "value");
params.put("more", "data");
client.get("http://www.google.com", params, new
TextHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, String response) {
System.out.println(response);
}
@Override
public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
Log.d("ERROR", error);
}
}
);
同樣的方式,你可以發送json請求,代碼如下:
String url = "https://ajax.googleapis.com/ajax/services/search/images";
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.put("q", "android");
params.put("rsz", "8");
client.get(url, params, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
// Handle resulting parsed JSON response here
}
@Override
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
// Handle resulting parsed JSON response here
}
});
看到了沒,返回的response已經自動轉化成JSONObject了,當然也支持JSONArray類型,override你需要的那個版本就行。
有了AsyncHttpClient,要實現這些功能是不是很簡單呢?當然這里只是很初級的介紹和使用,剩下的還需要開發者自己參考官方
文檔、源碼(官方甚至提供了一個Sample使用的集合),在實際項目中實踐。最后,強烈建議大家使用,是時候和冗長乏味的代碼說
我大概瀏覽了下其代碼,關鍵部分可以分為這4個模塊:
1. AsyncHttpClient自己一個模塊;
2. AsyncHttpRequest和RequestHandler一個模塊;
3. AsyncHttpResponseHandler及其各種特定子類一個模塊;
4. RetryHandler,自動重試機制。
我們可以很清楚的看出門道來,大體是按照client、request、response,這樣的方式組織的。接下來我們的代碼分析也就按照這個順序進行。
先來說AsyncHttpClient,來看其關鍵字段和ctor,代碼如下:
public static final String LOG_TAG = "AsyncHttpClient";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_RANGE = "Content-Range";
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
public static final String ENCODING_GZIP = "gzip";
public static final int DEFAULT_MAX_CONNECTIONS = 10;
public static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000;
public static final int DEFAULT_MAX_RETRIES = 5;
public static final int DEFAULT_RETRY_SLEEP_TIME_MILLIS = 1500;
public static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192;
private int maxConnections = DEFAULT_MAX_CONNECTIONS;
private int connectTimeout = DEFAULT_SOCKET_TIMEOUT;
private int responseTimeout = DEFAULT_SOCKET_TIMEOUT; // 各種參數設置
private final DefaultHttpClient httpClient; // 包裝的Apache DefaultHttpClient
private final HttpContext httpContext;
private ExecutorService threadPool; // 執行網絡請求的線程池
private final Map<Context, List<RequestHandle>> requestMap; // 與Android Context對應的請求map
private final Map<String, String> clientHeaderMap; // 客戶端的請求header map
private boolean isUrlEncodingEnabled = true; // 允許url encoding
接下來看看各種ctor,如下:
/**
* Creates a new AsyncHttpClient with default constructor arguments values
*/
public AsyncHttpClient() { // 一般客戶端代碼中都直接調用這個版本的ctor
this(false, 80, 443);
}
/**
* Creates a new AsyncHttpClient.
*
* @param httpPort non-standard HTTP-only port
*/
public AsyncHttpClient(int httpPort) {
this(false, httpPort, 443);
}
/**
* Creates a new AsyncHttpClient.
*
* @param httpPort non-standard HTTP-only port
* @param httpsPort non-standard HTTPS-only port
*/
public AsyncHttpClient(int httpPort, int httpsPort) {
this(false, httpPort, httpsPort);
}
/**
* Creates new AsyncHttpClient using given params
*
* @param fixNoHttpResponseException Whether to fix or not issue, by omitting SSL verification
* @param httpPort HTTP port to be used, must be greater than 0
* @param httpsPort HTTPS port to be used, must be greater than 0
*/
public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
this(getDefaultSchemeRegistry(fixNoHttpResponseException, httpPort, httpsPort));
}
/**
* Returns default instance of SchemeRegistry
*
* @param fixNoHttpResponseException Whether to fix or not issue, by omitting SSL verification
* @param httpPort HTTP port to be used, must be greater than 0
* @param httpsPort HTTPS port to be used, must be greater than 0
*/
private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
if (fixNoHttpResponseException) { // 如果你請求的url是https的,並且遇到了SSL驗證之類的錯誤,那么你應該將此值設為true試試
Log.d(LOG_TAG, "Beware! Using the fix is insecure, as it doesn't verify SSL certificates.");
}
if (httpPort < 1) {
httpPort = 80;
Log.d(LOG_TAG, "Invalid HTTP port number specified, defaulting to 80");
}
if (httpsPort < 1) {
httpsPort = 443;
Log.d(LOG_TAG, "Invalid HTTPS port number specified, defaulting to 443");
}
// Fix to SSL flaw in API < ICS
// See https://code.google.com/p/android/issues/detail?id=13117
SSLSocketFactory sslSocketFactory;
if (fixNoHttpResponseException) { // 感興趣的同學可自行看看MySSLSocketFactory的實現,基本上是省略了SSL驗證環節
sslSocketFactory = MySSLSocketFactory.getFixedSocketFactory();
} else {
sslSocketFactory = SSLSocketFactory.getSocketFactory();
}
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), httpPort));
schemeRegistry.register(new Scheme("https", sslSocketFactory, httpsPort));
return schemeRegistry;
}
/**
* Creates a new AsyncHttpClient.
*
* @param schemeRegistry SchemeRegistry to be used
*/
public AsyncHttpClient(SchemeRegistry schemeRegistry) { // 最終調到的是這個版本。。。
BasicHttpParams httpParams = new BasicHttpParams();
// 接下來是設置各種參數。。。
ConnManagerParams.setTimeout(httpParams, connectTimeout);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections));
ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
HttpConnectionParams.setSoTimeout(httpParams, responseTimeout);
HttpConnectionParams.setConnectionTimeout(httpParams, connectTimeout);
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
// 初始化關鍵字段
threadPool = getDefaultThreadPool();
requestMap = Collections.synchronizedMap(new WeakHashMap<Context, List<RequestHandle>>());
clientHeaderMap = new HashMap<String, String>();
httpContext = new SyncBasicHttpContext(new BasicHttpContext());
httpClient = new DefaultHttpClient(cm, httpParams);
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) {
if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) {
request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
for (String header : clientHeaderMap.keySet()) {
if (request.containsHeader(header)) {
Header overwritten = request.getFirstHeader(header);
Log.d(LOG_TAG,
String.format("Headers were overwritten! (%s | %s) overwrites (%s | %s)",
header, clientHeaderMap.get(header),
overwritten.getName(), overwritten.getValue())
);
//remove the overwritten header
request.removeHeader(overwritten);
}
request.addHeader(header, clientHeaderMap.get(header));
}
}
});
httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
@Override
public void process(HttpResponse response, HttpContext context) {
final HttpEntity entity = response.getEntity();
if (entity == null) {
return;
}
final Header encoding = entity.getContentEncoding();
if (encoding != null) {
for (HeaderElement element : encoding.getElements()) {
if (element.getName().equalsIgnoreCase(ENCODING_GZIP)) {
response.setEntity(new InflatingEntity(entity));
break;
}
}
}
}
});
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.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);
// 設置重試Handler,會在合適的情況下自動重試
httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_SLEEP_TIME_MILLIS));
}
接下來重要的就是各種HTTP head、get、post、delete方法,它們最終調用的都是sendRequest方法,如下:
/**
* Puts a new request in queue as a new thread in pool to be executed
*
* @param client HttpClient to be used for request, can differ in single requests
* @param contentType MIME body type, for POST and PUT requests, may be null
* @param context Context of Android application, to hold the reference of request
* @param httpContext HttpContext in which the request will be executed
* @param responseHandler ResponseHandler or its subclass to put the response into
* @param uriRequest instance of HttpUriRequest, which means it must be of HttpDelete,
* HttpPost, HttpGet, HttpPut, etc.
* @return RequestHandle of future request process
*/
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");
}
if (responseHandler == null) {
throw new IllegalArgumentException("ResponseHandler must not be null");
}
if (responseHandler.getUseSynchronousMode()) {
throw new IllegalArgumentException("Synchronous ResponseHandler used in AsyncHttpClient. You should create your response handler in a looper thread or use SyncHttpClient instead.");
}
if (contentType != null) {
uriRequest.setHeader(HEADER_CONTENT_TYPE, contentType);
}
responseHandler.setRequestHeaders(uriRequest.getAllHeaders());
responseHandler.setRequestURI(uriRequest.getURI());
// 下面的這3行是重點,創建請求,提交請求到線程池,將請求包裝到RequestHandle用於之后的取消、管理
AsyncHttpRequest request = newAsyncHttpRequest(client, httpContext, uriRequest, contentType, responseHandler, context);
threadPool.submit(request); // 能submit說明request至少是個Runnable
RequestHandle requestHandle = new RequestHandle(request);
if (context != null) { // 如果Android context非空的話,做一些關聯操作,后面可以通過context來取消request的執行
// Add request to request map
List<RequestHandle> requestList = requestMap.get(context);
synchronized (requestMap) {
if (requestList == null) {
requestList = Collections.synchronizedList(new LinkedList<RequestHandle>());
requestMap.put(context, requestList);
}
}
if (responseHandler instanceof RangeFileAsyncHttpResponseHandler)
((RangeFileAsyncHttpResponseHandler) responseHandler).updateRequestHeaders(uriRequest);
requestList.add(requestHandle);
Iterator<RequestHandle> iterator = requestList.iterator();
while (iterator.hasNext()) {
if (iterator.next().shouldBeGarbageCollected()) {
iterator.remove(); // 清理已經完成/取消了的請求
}
}
}
return requestHandle;
}
看到了吧,發送請求的過程其實重點是創建請求,然后submit到線程池,剩下的事情就交給線程池自己處理了,我們只需要坐等被調用。
來看看創建請求的方法,代碼如下:
/**
* Instantiate a new asynchronous HTTP request for the passed parameters.
*
* @param client HttpClient to be used for request, can differ in single requests
* @param contentType MIME body type, for POST and PUT requests, may be null
* @param context Context of Android application, to hold the reference of request
* @param httpContext HttpContext in which the request will be executed
* @param responseHandler ResponseHandler or its subclass to put the response into
* @param uriRequest instance of HttpUriRequest, which means it must be of HttpDelete,
* HttpPost, HttpGet, HttpPut, etc.
* @return AsyncHttpRequest ready to be dispatched
*/
protected AsyncHttpRequest newAsyncHttpRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) {
return new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler);
}
緊接着我們看看AsyncHttpRequest的實現:
/**
* Internal class, representing the HttpRequest, done in asynchronous manner
*/
public class AsyncHttpRequest implements Runnable { // 這就是submit到線程池的Runnable
private final AbstractHttpClient client;
private final HttpContext context;
private final HttpUriRequest request;
private final ResponseHandlerInterface responseHandler;
private int executionCount;
private boolean isCancelled;
private boolean cancelIsNotified;
private boolean isFinished;
private boolean isRequestPreProcessed;
public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, ResponseHandlerInterface responseHandler) {
this.client = client;
this.context = context;
this.request = request;
this.responseHandler = responseHandler;
}
/**
* This method is called once by the system when the request is about to be
* processed by the system. The library makes sure that a single request
* is pre-processed only once.
*
* Please note: pre-processing does NOT run on the main thread, and thus
* any UI activities that you must perform should be properly dispatched to
* the app's UI thread.
*
* @param request The request to pre-process
*/
public void onPreProcessRequest(AsyncHttpRequest request) {
// default action is to do nothing...
}
/**
* This method is called once by the system when the request has been fully
* sent, handled and finished. The library makes sure that a single request
* is post-processed only once.
*
* Please note: post-processing does NOT run on the main thread, and thus
* any UI activities that you must perform should be properly dispatched to
* the app's UI thread.
*
* @param request The request to post-process
*/
public void onPostProcessRequest(AsyncHttpRequest request) {
// default action is to do nothing...
}
@Override
public void run() { // 這是在線程池中執行的方法,我們重點看看
if (isCancelled()) { // 檢測,如果已經取消了則直接返回,下面的代碼有好多次做這個檢測,因為你永遠不知道什么時候會被取消
return; // 同時也說明了我們的Request是支持取消的
}
// Carry out pre-processing for this request only once.
if (!isRequestPreProcessed) {
isRequestPreProcessed = true;
onPreProcessRequest(this); // callback接口,在一次請求中只調用一次
}
if (isCancelled()) { // 再次檢查
return;
}
if (responseHandler != null) {
responseHandler.sendStartMessage(); // 發送開始請求消息
}
if (isCancelled()) { // 檢查
return;
}
try {
makeRequestWithRetries(); // 帶自動retry機制的請求
} catch (IOException e) {
if (!isCancelled() && responseHandler != null) {
responseHandler.sendFailureMessage(0, null, null, e); // 在沒取消的情況下,發送失敗消息
} else {
Log.e("AsyncHttpRequest", "makeRequestWithRetries returned error, but handler is null", e);
}
}
if (isCancelled()) { // 檢查again
return;
}
if (responseHandler != null) { // 沒取消的情況下,發送完成消息
responseHandler.sendFinishMessage();
}
if (isCancelled()) {
return;
}
// Carry out post-processing for this request.
onPostProcessRequest(this); // 處理了請求之后的callback
isFinished = true; // 設置為true表示這個請求執行完畢了
}
private void makeRequest() throws IOException { // 發送一次請求
if (isCancelled()) {
return;
}
// Fixes #115
if (request.getURI().getScheme() == null) {
// subclass of IOException so processed in the caller
throw new MalformedURLException("No valid URI scheme was provided");
}
// 執行請求獲得response
HttpResponse response = client.execute(request, context);
if (isCancelled() || responseHandler == null) {
return;
}
// Carry out pre-processing for this response.
responseHandler.onPreProcessResponse(responseHandler, response); // 處理response前
if (isCancelled()) {
return;
}
// The response is ready, handle it.
responseHandler.sendResponseMessage(response); // 發送獲得的response
if (isCancelled()) {
return;
}
// Carry out post-processing for this response.
responseHandler.onPostProcessResponse(responseHandler, response); // 處理response后
}
private void makeRequestWithRetries() throws IOException {
boolean retry = true;
IOException cause = null;
HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
try {
while (retry) { // 注意這個循環,當retry為false的時候退出
try {
makeRequest();
return; // 請求成功的話,直接返回
} catch (UnknownHostException e) {
// switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException
// while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry
// (to assist in genuine cases of unknown host) which seems better than outright failure
cause = new IOException("UnknownHostException exception: " + e.getMessage());
retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context);
} catch (NullPointerException e) {
// there's a bug in HttpClient 4.0.x that on some occasions causes
// DefaultRequestExecutor to throw an NPE, see
// http://code.google.com/p/android/issues/detail?id=5255
cause = new IOException("NPE in HttpClient: " + e.getMessage());
retry = retryHandler.retryRequest(cause, ++executionCount, context);
} catch (IOException e) {
if (isCancelled()) {
// Eating exception, as the request was cancelled
return;
}
cause = e;
retry = retryHandler.retryRequest(cause, ++executionCount, context);
}
// 各種異常的情況下,計算retry,看還是否需要retry
if (retry && (responseHandler != null)) { // 需要retry的時候,發送retry消息並附帶第幾次retry了
responseHandler.sendRetryMessage(executionCount);
}
}
} catch (Exception e) {
// catch anything else to ensure failure message is propagated
Log.e("AsyncHttpRequest", "Unhandled exception origin cause", e);
// 其他的所有不在上述catch里的異常都在這里統一包裝成IOException,在最后拋出
cause = new IOException("Unhandled exception: " + e.getMessage());
}
// cleaned up to throw IOException
throw (cause); // 拋出,以便上層代碼知道發生了什么
}
public boolean isCancelled() {
if (isCancelled) {
sendCancelNotification();
}
return isCancelled;
}
private synchronized void sendCancelNotification() {
if (!isFinished && isCancelled && !cancelIsNotified) {
cancelIsNotified = true;
if (responseHandler != null)
responseHandler.sendCancelMessage();
}
}
public boolean isDone() {
return isCancelled() || isFinished;
}
public boolean cancel(boolean mayInterruptIfRunning) {
isCancelled = true;
request.abort();
return isCancelled();
}
}
緊接着,我們大概提下RequestHandle,它只是一個持有AsyncHttpRequest對象的弱引用,其方法內部都delegate給了AsyncHttpRequest,
非常簡單,感興趣的同學可自行閱讀。
看完了Request,接下來該看看各種Response了,他們都實現了ResponseHandlerInterface接口,這里我們重點看下AsyncHttpResponseHandler,
因為它是后面所有更具體的子類的基礎,其ctor代碼如下:
/**
* Creates a new AsyncHttpResponseHandler
*/
public AsyncHttpResponseHandler() { // 不指定looper
this(null);
}
/**
* Creates a new AsyncHttpResponseHandler with a user-supplied looper. If
* the passed looper is null, the looper attached to the current thread will
* be used.
*
* @param looper The looper to work with
*/
public AsyncHttpResponseHandler(Looper looper) { // 如果沒指定looper的話,會用當前線程的looper頂替
this.looper = looper == null ? Looper.myLooper() : looper;
// Use asynchronous mode by default.
setUseSynchronousMode(false); // 默認是異步的方式,這里異步的意思是指對response的處理發生在與looper
} // 關聯的線程中,而不是請求發生的線程池里的線程中
@Override
public void setUseSynchronousMode(boolean sync) {
// A looper must be prepared before setting asynchronous mode.
if (!sync && this.looper == null) {
sync = true; // 一種錯誤的情況,強制使用同步mode
Log.w(LOG_TAG, "Current thread has not called Looper.prepare(). Forcing synchronous mode.");
}
// If using asynchronous mode.
if (!sync && handler == null) { // 初始化handler
// Create a handler on current thread to submit tasks
handler = new ResponderHandler(this, this.looper);
} else if (sync && handler != null) {
// TODO: Consider adding a flag to remove all queued messages.
handler = null;
}
useSynchronousMode = sync;
}
一般來說,我們會直接在UI線程中調用無參版本的ctor,也就是說response是和UI線程關聯的,所有對其的處理handleMessage是發生
在UI線程中的。如果你想用response的結果來更新UI則這是正確的方式。
接着我們看看和處理response相關的代碼:
/**
* Avoid leaks by using a non-anonymous handler class.
*/
private static class ResponderHandler extends Handler {
private final AsyncHttpResponseHandler mResponder;
ResponderHandler(AsyncHttpResponseHandler mResponder, Looper looper) {
super(looper);
this.mResponder = mResponder;
}
@Override
public void handleMessage(Message msg) { // 一個簡單的Handler,其handleMessage delegate給了mResponder
mResponder.handleMessage(msg);
}
}
// Methods which emulate android's Handler and Message methods
protected void handleMessage(Message message) { // 對各種message的處理,回調各種onXXX方法
Object[] response;
switch (message.what) {
case SUCCESS_MESSAGE:
response = (Object[]) message.obj;
if (response != null && response.length >= 3) {
onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]);
} else {
Log.e(LOG_TAG, "SUCCESS_MESSAGE didn't got enough params");
}
break;
case FAILURE_MESSAGE:
response = (Object[]) message.obj;
if (response != null && response.length >= 4) {
onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]);
} else {
Log.e(LOG_TAG, "FAILURE_MESSAGE didn't got enough params");
}
break;
case START_MESSAGE:
onStart();
break;
case FINISH_MESSAGE:
onFinish();
break;
case PROGRESS_MESSAGE:
response = (Object[]) message.obj;
if (response != null && response.length >= 2) {
try {
onProgress((Integer) response[0], (Integer) response[1]);
} catch (Throwable t) {
Log.e(LOG_TAG, "custom onProgress contains an error", t);
}
} else {
Log.e(LOG_TAG, "PROGRESS_MESSAGE didn't got enough params");
}
break;
case RETRY_MESSAGE:
response = (Object[]) message.obj;
if (response != null && response.length == 1) {
onRetry((Integer) response[0]);
} else {
Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params");
}
break;
case CANCEL_MESSAGE:
onCancel();
break;
}
}
protected void sendMessage(Message msg) {
if (getUseSynchronousMode() || handler == null) {
handleMessage(msg); // 如果是同步的方式,則handleMessage發生在調用sendMessage的線程中
} else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled
handler.sendMessage(msg); // 否則發生在與handler關聯的線程中,一般多為UI線程
}
}
代碼中各種sendXXXMessage都會調用這里的sendMessage方法,只是構造的msg的what、obj不同而已。而sendXXXMessage方法
會在request的不同階段自動被調用,詳見AsyncHttpRequest中。下一步我們看眼對response的解析過程,代碼如下:
@Override
public void sendResponseMessage(HttpResponse response) throws IOException {
// do not process if request has been cancelled
if (!Thread.currentThread().isInterrupted()) {
StatusLine status = response.getStatusLine();
byte[] responseBody;
responseBody = getResponseData(response.getEntity()); // 將response解析成字節數組
// additional cancellation check as getResponseData() can take non-zero time to process
if (!Thread.currentThread().isInterrupted()) {
if (status.getStatusCode() >= 300) { // 標志失敗的情況
sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()));
} else { // 成功的情況
sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody);
}
}
}
}
/**
* Returns byte array of response HttpEntity contents
*
* @param entity can be null
* @return response entity body or null
* @throws java.io.IOException if reading entity or creating byte array failed
*/
byte[] getResponseData(HttpEntity entity) throws IOException {
byte[] responseBody = null;
if (entity != null) {
InputStream instream = entity.getContent(); // 從entity中讀取字節流
if (instream != null) {
long contentLength = entity.getContentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalArgumentException("HTTP entity too large to be buffered in memory");
}
int buffersize = (contentLength <= 0) ? BUFFER_SIZE : (int) contentLength;
try {
ByteArrayBuffer buffer = new ByteArrayBuffer(buffersize);
try {
byte[] tmp = new byte[BUFFER_SIZE];
int l, count = 0;
// do not send messages if request has been cancelled
while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) {
count += l;
buffer.append(tmp, 0, l);
sendProgressMessage(count, (int) (contentLength <= 0 ? 1 : contentLength));
}
} finally {
AsyncHttpClient.silentCloseInputStream(instream);
AsyncHttpClient.endEntityViaReflection(entity);
}
responseBody = buffer.toByteArray();
} catch (OutOfMemoryError e) {
System.gc();
throw new IOException("File too large to fit into available memory");
}
}
}
return responseBody;
}
onXXX方法除了onSuccess和onFailure外都做了默認實現即啥也不做,所以繼承至它的子類至少要實現這2個方法,其他的方法你可以選擇性實現。
接下來我們看看TextHttpResponseHandler子類的實現,關鍵代碼如下:
@Override // 對上述2個方法的重載,其中將byte[]通過getResponseString方法轉化成了String對象
public void onSuccess(int statusCode, Header[] headers, byte[] responseBytes) {
onSuccess(statusCode, headers, getResponseString(responseBytes, getCharset()));
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
onFailure(statusCode, headers, getResponseString(responseBytes, getCharset()), throwable);
}
/**
* Attempts to encode response bytes as string of set encoding
*
* @param charset charset to create string with
* @param stringBytes response bytes
* @return String of set encoding or null
*/
public static String getResponseString(byte[] stringBytes, String charset) {
try {
String toReturn = (stringBytes == null) ? null : new String(stringBytes, charset);
if (toReturn != null && toReturn.startsWith(UTF8_BOM)) {
return toReturn.substring(1);
}
return toReturn;
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Encoding response into string failed", e);
return null;
}
}
說白了,也就是在父類基礎上多了一層處理,將byte[]根據特定的編碼轉化成String而已,類似的JsonHttpResponseHandler又在此基礎上
將String轉化成JSONObject或JSONArray,細節不贅述。
ResponseHandler介紹完了,這里我們提下RetryHandler,這個類也很簡單,根據內部的白/黑名單等規則來確定是否要retry。
AsyncHttpClient當然也提供了對Cookie的支持,默認是保存在Android的SharedPreferences中,具體代碼見PersistentCookieStore。
還有一個功能豐富的RequestParams類,據此你不僅可以為GET/POST方法提供參數,甚至你可以上傳本地文件到server端。
http://www.cnblogs.com/xiaoweiz/p/3918042.html

