由于我们设计的是异步的网络请求框架,因此代码实现使用的是基于接口回调的方式来处理从服务器端返回的数据,鉴于数据格式多种多样,例如有二进制文件,普通文本文件,json格式字符串 ,xml格式字符串,断点续传文件等等,因此从响应处理接口会派生出很多子类,专门处理不同格式的数据。从下面的UML类图可以看到响应处理器接口是ResponseHandlerInterface,从这个接口派生出AsyncHttpResponseHandler抽象类,它是所有其他ResponseHandler类的基类,基类基于Android的Handler类实现了整个Http响应的处理流程。各个派生子类根据特殊的数据格式自定义了数据解析部分的操作。下面就来详细解析每个接口和类。
[Http响应接口ResponseHandlerInterface]
ResponseHandlerInterface接口制定了Http响应处理流程的规约,例如当Http请求开始执行时会发送sendStartMessage,Http请求执行完成即将从线程池中移除时会发送sendFinishMessage,当请求需要实时更新进度,例如上传文件时,会不断发送sendProgressMessage,请求被取消会发送sendCancelMessage,服务器成功处理Http请求并返回数据后,会发送sendSuccessMessage,当Http请求出现错误时会发送sendFailureMessage,重试Http请求会发送sendRetryMessage。该接口完整定义如下所示:
package com.loopj.android.http;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.net.URI;
/**
* Interface to standardize implementations
*/
public interface ResponseHandlerInterface {
/**
* Returns data whether request completed successfully
*
* @param response HttpResponse object with data
* @throws java.io.IOException if retrieving data from response fails
*/
void sendResponseMessage(HttpResponse response) throws IOException;
/**
* Notifies callback, that request started execution
*/
void sendStartMessage();
/**
* Notifies callback, that request was completed and is being removed from thread pool
*/
void sendFinishMessage();
/**
* Notifies callback, that request (mainly uploading) has progressed
*
* @param bytesWritten number of written bytes
* @param bytesTotal number of total bytes to be written
*/
void sendProgressMessage(int bytesWritten, int bytesTotal);
/**
* Notifies callback, that request was cancelled
*/
void sendCancelMessage();
/**
* Notifies callback, that request was handled successfully
*
* @param statusCode HTTP status code
* @param headers returned headers
* @param responseBody returned data
*/
void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody);
/**
* Returns if request was completed with error code or failure of implementation
*
* @param statusCode returned HTTP status code
* @param headers returned headers
* @param responseBody returned data
* @param error cause of request failure
*/
void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error);
/**
* Notifies callback of retrying request
*
* @param retryNo number of retry within one request
*/
void sendRetryMessage(int retryNo);
/**
* Returns URI which was used to request
*
* @return uri of origin request
*/
public URI getRequestURI();
/**
* Returns Header[] which were used to request
*
* @return headers from origin request
*/
public Header[] getRequestHeaders();
/**
* Helper for handlers to receive Request URI info
*
* @param requestURI claimed request URI
*/
public void setRequestURI(URI requestURI);
/**
* Helper for handlers to receive Request Header[] info
*
* @param requestHeaders Headers, claimed to be from original request
*/
public void setRequestHeaders(Header[] requestHeaders);
/**
* Can set, whether the handler should be asynchronous or synchronous
*
* @param useSynchronousMode whether data should be handled on background Thread on UI Thread
*/
void setUseSynchronousMode(boolean useSynchronousMode);
/**
* Returns whether the handler is asynchronous or synchronous
*
* @return boolean if the ResponseHandler is running in synchronous mode
*/
boolean getUseSynchronousMode();
/**
* This method is called once by the system when the response is about to be
* processed by the system. The library makes sure that a single response
* 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 instance An instance of this response object
* @param response The response to pre-processed
*/
void onPreProcessResponse(ResponseHandlerInterface instance, HttpResponse response);
/**
* 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 response
* 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 instance An instance of this response object
* @param response The response to post-process
*/
void onPostProcessResponse(ResponseHandlerInterface instance, HttpResponse response);
}
[抽象基类AsyncHttpResponseHandler]
AsyncHttpResponseHandler实现了ResponseHandlerInterface接口,基于Handler,Looper和Message机制实现整个Http请求和响应消息的循环,与ResponseHandlerInterface接口中定义的send*Message系列函数相对应,在抽象类中定义了如下几种消息类型:
protected static final int SUCCESS_MESSAGE = 0;
protected static final int FAILURE_MESSAGE = 1;
protected static final int START_MESSAGE = 2;
protected static final int FINISH_MESSAGE = 3;
protected static final int PROGRESS_MESSAGE = 4;
protected static final int RETRY_MESSAGE = 5;
protected static final int CANCEL_MESSAGE = 6;
同时,为了防止Handler的内存泄漏,我们定义了一个静态内部类ResponseHandler,继承自Handler,来处理整个AsyncHttpResponseHandler的消息循环:
/**
* 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) {
mResponder.handleMessage(msg);
}
}
在ResponseHandler的handleMessage重载函数中,将消息循环委托给AsyncHttpResponseHandler实例的handleMessage函数来处理,handleMessage的作用是分发不同的消息类型,同时对消息附带的数据格式进行校验
// Methods which emulate android's Handler and Message methods
protected void handleMessage(Message message) {
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;
}
}
接下来就逐个解析每个消息的发送和处理流程:
[消息类型:START_MESSAGE,发送函数:sendStartMessage,处理函数:onStart]
@Override
final public void sendStartMessage() {
sendMessage(obtainMessage(START_MESSAGE, null));
}
/**
* Fired when the request is started, override to handle in your own code
*/
public void onStart() {
// default log warning is not necessary, because this method is just optional notification
}
可以看到,START_MESSAGE的作用只是简单的通知开发者,http请求开始执行了,因此在sendStartMessage函数中obtainMessage的数据参数是空的,onStart默认是空实现,AsyncHttpResponseHandler的子类可以选择是否覆写该函数,对于没有用到这个函数的子类,可以直接使用基类的实现即可。
[消息类型:FINISH_MESSAGE,发送函数:sendFinishMessage,处理函数:onFinish]
@Override
final public void sendFinishMessage() {
sendMessage(obtainMessage(FINISH_MESSAGE, null));
}
/**
* Fired in all cases when the request is finished, after both success and failure, override to
* handle in your own code
*/
public void onFinish() {
// default log warning is not necessary, because this method is just optional notification
}
同START_MESSAGE,FINISH_MESSAGE的作用是通知开发者http请求执行结束,无论是成功还是失败都会通知,sendFinishMessage中obtainMessage的数据参数也是空的,onFinish函数也是空实现。
[消息类型:SUCCESS_MESSAGE,发送函数:sendSuccessMessage,处理函数:onSuccess]
@Override
final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) {
sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes}));
}
/**
* Fired when a request returns successfully, override to handle in your own code
*
* @param statusCode the status code of the response
* @param headers return headers, if any
* @param responseBody the body of the HTTP response from the server
*/
public abstract void onSuccess(int statusCode, Header[] headers, byte[] responseBody);
SendSuccessMessage表示Http响应成功,需要处理服务器返回的错误码,Http响应头以及响应数据实体,这分别对应参数statusCode,headers和responseBytes。由于onSuccess是每个子类都需要重载进行处理的,因此定义成了抽象函数,强制每个子类实现它。
[消息类型:FAILURE_MESSAGE,发送函数:sendFailureMessage,处理函数:onFailure]
@Override
final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable throwable) {
sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, throwable}));
}
/**
* Fired when a request fails to complete, override to handle in your own code
*
* @param statusCode return HTTP status code
* @param headers return headers, if any
* @param responseBody the response body, if any
* @param error the underlying cause of the failure
*/
public abstract void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error);
SendFailureMessage函数处理Http请求失败的情况,除了与SendSuccessMessage一样具有statusCode,headers和responseBody之外,还多了一个异常参数throwable。
[消息类型:PROGRESS_MESSAGE,发送参数:sendProgressMessage,处理函数:onProgress]
@Override
final public void sendProgressMessage(int bytesWritten, int bytesTotal) {
sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal}));
}
/**
* Fired when the request progress, override to handle in your own code
*
* @param bytesWritten offset from start of file
* @param totalSize total size of file
*/
public void onProgress(int bytesWritten, int totalSize) {
Log.v(LOG_TAG, String.format("Progress %d from %d (%2.0f%%)", bytesWritten, totalSize, (totalSize > 0) ? (bytesWritten * 1.0 / totalSize) * 100 : -1));
}
由于是断点续传类型的消息,因此sendProgressMessage函数参数有两个,bytesWritten表示已经接收的数据长度,bytesTotal表示文件总的长度,这样就可以方便的在UI界面上实时显示文件下载活着上传的百分比信息了。
[消息类型:RETRY_MESSAGE,发送参数:sendRetryMesssage,处理函数:onRetry]
@Override
final public void sendRetryMessage(int retryNo) {
sendMessage(obtainMessage(RETRY_MESSAGE, new Object[]{retryNo}));
}
/**
* Fired when a retry occurs, override to handle in your own code
*
* @param retryNo number of retry
*/
public void onRetry(int retryNo) {
Log.d(LOG_TAG, String.format("Request retry no. %d", retryNo));
}
当发送Http请求失败时,如果设置了重试次数,那么就会发出RETRY_MESSAGE消息,直到重试次数达到最大值为止。重试非必需,因此onRetry回调函数默认空实现,子类可以不重写。
[消息类型:CANCEL_MESSAGE,发送参数:sendCancelMessage,处理函数:onCancel]
@Override
final public void sendCancelMessage() {
sendMessage(obtainMessage(CANCEL_MESSAGE, null));
}
public void onCancel() {
Log.d(LOG_TAG, "Request got cancelled");
}
当Http请求发出但还没处理完成时,用户选择取消请求,这时会发出sendCancelMessage消息,并在onCancel回调函数中处理取消操作。
AsyncHttpResponseHandler定义了两个构造函数,分别如下所示:
/**
* Creates a new AsyncHttpResponseHandler
*/
public AsyncHttpResponseHandler() {
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) {
this.looper = looper == null ? Looper.myLooper() : looper;
// Use asynchronous mode by default.
setUseSynchronousMode(false);
}
可以指定自定义的Looper,也可以使用当前线程的Looper,在构造函数中默认设置同步模式为异步,在设置同步模式为异步之前,必须保证当前线程的looper不为空,否则强制转换为同步模式。如果是异步模式,那么就实例化ResponseHandler用来处理异步消息,同步模式下handler设置为null即可,设置同步模式代码如下:
@Override
public void setUseSynchronousMode(boolean sync) {
// A looper must be prepared before setting asynchronous mode.
if (!sync && looper == null) {
sync = true;
Log.w(LOG_TAG, "Current thread has not called Looper.prepare(). Forcing synchronous mode.");
}
// If using asynchronous mode.
if (!sync && handler == null) {
// Create a handler on current thread to submit tasks
handler = new ResponderHandler(this, looper);
} else if (sync && handler != null) {
// TODO: Consider adding a flag to remove all queued messages.
handler = null;
}
useSynchronousMode = sync;
}
在基类中还定义了两个工具函数,供子类共同使用,它们分别是从当前Handler实例中创建Message消息实例的obtainMessage函数以及执行runnable实例的postRunnable函数,postRunnable函数会根据当前的同步模式选择runnable实例的执行方式,如果是同步模式活着handler实例为空,那么就直接在当前线程执行runnable.run函数;如果是异步模式,那么就将runnable发送给handler来处理。
/**
* Helper method to send runnable into local handler loop
*
* @param runnable runnable instance, can be null
*/
protected void postRunnable(Runnable runnable) {
if (runnable != null) {
if (getUseSynchronousMode() || handler == null) {
// This response handler is synchronous, run on current thread
runnable.run();
} else {
// Otherwise, run on provided handler
AssertUtils.asserts(handler != null, "handler should not be null!");
handler.post(runnable);
}
}
}
/**
* Helper method to create Message instance from handler
*
* @param responseMessageId constant to identify Handler message
* @param responseMessageData object to be passed to message receiver
* @return Message instance, should not be null
*/
protected Message obtainMessage(int responseMessageId, Object responseMessageData) {
return Message.obtain(handler, responseMessageId, responseMessageData);
}
[文本格式响应处理器基类TextHttpResponseHandler]
该子类用于专门处理String类型的响应数据内容,既然是字符串,那首先会涉及到编码问题,因此在构造函数中可以指定字符编码,默认是UTF-8。子类重写了基类的onSuccess和onFailure函数,并将参数中的字节数组转换成字符串,这个转换是在getResponseString函数中完成的:
public static final String UTF8_BOM = "\uFEFF";
/**
* 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;
}
}
可以看到,上面代码中专门针对UTF-8 BOM作了处理,什么是UTF-8 BOM呢?简单的讲,BOM(byte order mark)是为UTF-16和UTF-32准备的,用于标记字节序,即在文本文件开头放置了特殊字符\uFEFF,在UTF-8文件中放置BOM头主要出现在Windows系统上,这样是微软为了把UTF-8和ASCII等编码明确区分开来,但这样的文件在其他系统上会出现问题。因此我们将字节数组转换成字符串后,会判断字符串开头是否包含\uFEFF字符,如果存在,则去掉它。TextHttpResponseHandler的完整定义如下:
public abstract class TextHttpResponseHandler extends AsyncHttpResponseHandler {
private static final String LOG_TAG = "TextHttpResponseHandler";
/**
* Creates new instance with default UTF-8 encoding
*/
public TextHttpResponseHandler() {
this(DEFAULT_CHARSET);
}
/**
* Creates new instance with given string encoding
*
* @param encoding String encoding, see {@link #setCharset(String)}
*/
public TextHttpResponseHandler(String encoding) {
super();
setCharset(encoding);
}
/**
* Called when request fails
*
* @param statusCode http response status line
* @param headers response headers if any
* @param responseString string response of given charset
* @param throwable throwable returned when processing request
*/
public abstract void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable);
/**
* Called when request succeeds
*
* @param statusCode http response status line
* @param headers response headers if any
* @param responseString string response of given charset
*/
public abstract void onSuccess(int statusCode, Header[] headers, String responseString);
@Override
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;
}
}
}
[Android内置Json格式解析器响应处理器类JsonHttpResponseHandler]
JsonHttpResponseHandler继承自TextHttpResponseHandler类,专门为解析Json数据格式而实现的子类,该类完成的主要功能就是将字节数组转换成JSONObject或者JSONArray,该功能在parseResponse函数中完成,在该函数中首先会调用父类的getResponseString函数将字节数组转换成String类型,然后去掉UTF-8 BOM头部字符(这一步多余了,因为在getResponseString中已经处理了),最后判断是否是标准的Json格式,如果Json转换失败,则直接返回原始字符串。
/**
* Returns Object of type {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
* Double or {@link JSONObject#NULL}, see {@link org.json.JSONTokener#nextValue()}
*
* @param responseBody response bytes to be assembled in String and parsed as JSON
* @return Object parsedResponse
* @throws org.json.JSONException exception if thrown while parsing JSON
*/
protected Object parseResponse(byte[] responseBody) throws JSONException {
if (null == responseBody)
return null;
Object result = null;
//trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If JSON is not valid this will return null
String jsonString = getResponseString(responseBody, getCharset());
if (jsonString != null) {
jsonString = jsonString.trim();
if (jsonString.startsWith(UTF8_BOM)) {
jsonString = jsonString.substring(1);
}
if (jsonString.startsWith("{") || jsonString.startsWith("[")) {
result = new JSONTokener(jsonString).nextValue();
}
}
if (result == null) {
result = jsonString;
}
return result;
}
重写父类的onSuccess和onFailure函数,在这两个函数中除了判断转换后的数据是否是Json格式并作相应的处理外,还针对同步模式做了分支处理,即新建Runnable实例进行具体的格式转换和消息分发,然后如果是同步模式,则直接在当前线程执行Runnable实例的run方法,如果是异步模式则基于Runnable实例新建子线程进行处理。JsonHttpResponseHandler完整代码如下所示:
public class JsonHttpResponseHandler extends TextHttpResponseHandler {
private static final String LOG_TAG = "JsonHttpResponseHandler";
/**
* Creates new JsonHttpResponseHandler, with JSON String encoding UTF-8
*/
public JsonHttpResponseHandler() {
super(DEFAULT_CHARSET);
}
/**
* Creates new JsonHttpRespnseHandler with given JSON String encoding
*
* @param encoding String encoding to be used when parsing JSON
*/
public JsonHttpResponseHandler(String encoding) {
super(encoding);
}
/**
* Returns when request succeeds
*
* @param statusCode http response status line
* @param headers response headers if any
* @param response parsed response if any
*/
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONObject) was not overriden, but callback was received");
}
/**
* Returns when request succeeds
*
* @param statusCode http response status line
* @param headers response headers if any
* @param response parsed response if any
*/
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONArray) was not overriden, but callback was received");
}
/**
* Returns when request failed
*
* @param statusCode http response status line
* @param headers response headers if any
* @param throwable throwable describing the way request failed
* @param errorResponse parsed response if any
*/
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONObject) was not overriden, but callback was received", throwable);
}
/**
* Returns when request failed
*
* @param statusCode http response status line
* @param headers response headers if any
* @param throwable throwable describing the way request failed
* @param errorResponse parsed response if any
*/
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) {
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONArray) was not overriden, but callback was received", throwable);
}
@Override
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
Log.w(LOG_TAG, "onFailure(int, Header[], String, Throwable) was not overriden, but callback was received", throwable);
}
@Override
public void onSuccess(int statusCode, Header[] headers, String responseString) {
Log.w(LOG_TAG, "onSuccess(int, Header[], String) was not overriden, but callback was received");
}
@Override
public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBytes) {
if (statusCode != HttpStatus.SC_NO_CONTENT) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final Object jsonResponse = parseResponse(responseBytes);
postRunnable(new Runnable() {
@Override
public void run() {
if (jsonResponse instanceof JSONObject) {
onSuccess(statusCode, headers, (JSONObject) jsonResponse);
} else if (jsonResponse instanceof JSONArray) {
onSuccess(statusCode, headers, (JSONArray) jsonResponse);
} else if (jsonResponse instanceof String) {
onFailure(statusCode, headers, (String) jsonResponse, new JSONException("Response cannot be parsed as JSON data"));
} else {
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
}
}
});
} catch (final JSONException ex) {
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, ex, (JSONObject) null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
onSuccess(statusCode, headers, new JSONObject());
}
}
@Override
public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
if (responseBytes != null) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final Object jsonResponse = parseResponse(responseBytes);
postRunnable(new Runnable() {
@Override
public void run() {
if (jsonResponse instanceof JSONObject) {
onFailure(statusCode, headers, throwable, (JSONObject) jsonResponse);
} else if (jsonResponse instanceof JSONArray) {
onFailure(statusCode, headers, throwable, (JSONArray) jsonResponse);
} else if (jsonResponse instanceof String) {
onFailure(statusCode, headers, (String) jsonResponse, throwable);
} else {
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
}
}
});
} catch (final JSONException ex) {
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, ex, (JSONObject) null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)");
onFailure(statusCode, headers, throwable, (JSONObject) null);
}
}
/**
* Returns Object of type {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
* Double or {@link JSONObject#NULL}, see {@link org.json.JSONTokener#nextValue()}
*
* @param responseBody response bytes to be assembled in String and parsed as JSON
* @return Object parsedResponse
* @throws org.json.JSONException exception if thrown while parsing JSON
*/
protected Object parseResponse(byte[] responseBody) throws JSONException {
if (null == responseBody)
return null;
Object result = null;
//trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If JSON is not valid this will return null
String jsonString = getResponseString(responseBody, getCharset());
if (jsonString != null) {
jsonString = jsonString.trim();
if (jsonString.startsWith(UTF8_BOM)) {
jsonString = jsonString.substring(1);
}
if (jsonString.startsWith("{") || jsonString.startsWith("[")) {
result = new JSONTokener(jsonString).nextValue();
}
}
if (result == null) {
result = jsonString;
}
return result;
}
}
[其他类型Json格式解析器响应处理器基类BaseJsonHttpResponseHandler]
这个ResponseHandler类是为了兼容第三方Json格式解析库而实现的,如果应用中使用第三方JSON Parser例如GSON,Jackson JSON等来实现Json的解析,那么就不能使用上面介绍的JsonHttpResponseHandler类来处理服务器返回的数据,这时可以继承BaseJsonHttpResponseHandler这个抽象类来实现解析的需求。BaseJsonHttpResponseHandler使用泛型来兼容不同的第三方JSON解析库,它与JsonHttpResponseHandler唯一的不同在于parseResponse函数的实现,将该函数设置抽象函数,供子类实现具体逻辑。完整代码如下:
public abstract class BaseJsonHttpResponseHandler<JSON_TYPE> extends TextHttpResponseHandler {
private static final String LOG_TAG = "BaseJsonHttpResponseHandler";
/**
* Creates a new JsonHttpResponseHandler with default charset "UTF-8"
*/
public BaseJsonHttpResponseHandler() {
this(DEFAULT_CHARSET);
}
/**
* Creates a new JsonHttpResponseHandler with given string encoding
*
* @param encoding result string encoding, see <a href="http://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Charset</a>
*/
public BaseJsonHttpResponseHandler(String encoding) {
super(encoding);
}
/**
* Base abstract method, handling defined generic type
*
* @param statusCode HTTP status line
* @param headers response headers
* @param rawJsonResponse string of response, can be null
* @param response response returned by {@link #parseResponse(String, boolean)}
*/
public abstract void onSuccess(int statusCode, Header[] headers, String rawJsonResponse, JSON_TYPE response);
/**
* Base abstract method, handling defined generic type
*
* @param statusCode HTTP status line
* @param headers response headers
* @param throwable error thrown while processing request
* @param rawJsonData raw string data returned if any
* @param errorResponse response returned by {@link #parseResponse(String, boolean)}
*/
public abstract void onFailure(int statusCode, Header[] headers, Throwable throwable, String rawJsonData, JSON_TYPE errorResponse);
@Override
public final void onSuccess(final int statusCode, final Header[] headers, final String responseString) {
if (statusCode != HttpStatus.SC_NO_CONTENT) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final JSON_TYPE jsonResponse = parseResponse(responseString, false);
postRunnable(new Runnable() {
@Override
public void run() {
onSuccess(statusCode, headers, responseString, jsonResponse);
}
});
} catch (final Throwable t) {
Log.d(LOG_TAG, "parseResponse thrown an problem", t);
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, t, responseString, null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
onSuccess(statusCode, headers, null, null);
}
}
@Override
public final void onFailure(final int statusCode, final Header[] headers, final String responseString, final Throwable throwable) {
if (responseString != null) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final JSON_TYPE jsonResponse = parseResponse(responseString, true);
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, throwable, responseString, jsonResponse);
}
});
} catch (Throwable t) {
Log.d(LOG_TAG, "parseResponse thrown an problem", t);
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, throwable, responseString, null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
onFailure(statusCode, headers, throwable, null, null);
}
}
/**
* Should return deserialized instance of generic type, may return object for more vague
* handling
*
* @param rawJsonData response string, may be null
* @param isFailure indicating if this method is called from onFailure or not
* @return object of generic type or possibly null if you choose so
* @throws Throwable allows you to throw anything from within deserializing JSON response
*/
protected abstract JSON_TYPE parseResponse(String rawJsonData, boolean isFailure) throws Throwable;
}
下面我们以GSON为例说明怎么样实现基于GSON的HttpResponseHandler类,我们假设工程中已经引入GSON的jar包,如果还有对GSON不熟悉的同学请自行百度之,并自我反省一下。
/**
* 针对Gson进行的特例化
*
* @author asce1885
* @date 2014-03-03
*
* @param <T>
*/
public abstract class GsonHttpResponseHandler<T> extends BaseJsonHttpResponseHandler<T> {
private Class<T> clazz;
public GsonHttpResponseHandler(Class<T> clazz) {
this.clazz = clazz;
}
@Override
protected T parseResponse(String rawJsonData, boolean isFailure)
throws Throwable {
if (!isFailure && !TextUtils.isEmpty(rawJsonData)) {
return GSONUtils.parseJson(clazz, rawJsonData);
}
return null;
}
}
/**
* 封装Gson函数库
*
* @author asce1885
* @date 2014-03-03
*
*/
public class GSONUtils {
private static final String TAG = GSONUtils.class.getSimpleName();
public static Gson gson = new Gson();
public static <T> T parseJson(Class<T> cls, String json) {
try {
return gson.fromJson(json, cls);
} catch(JsonSyntaxException e) {
LogUtils.e(TAG, e.getMessage());
}
return null;
}
public static String toJson(Object src) {
try {
return gson.toJson(src);
} catch(JsonSyntaxException e) {
LogUtils.e(TAG, e.getMessage());
}
return null;
}
}
[二进制格式文件响应处理器类BinaryHttpResponseHandler]
该类主要处理二进制文件的下载请求,并设置了白名单,只有在名单内的文件类型才会进行处理,重写基类AsyncHttpResponseHandler的sendResponseMessage函数,增加了文件类型(content-type)过滤的功能,当存在content-type响应头,并且取值在白名单内时,则转给基类的sendResponseMessage函数做进一步的处理:
BinaryHttpResponseHandler的完整代码如下:
public abstract class BinaryHttpResponseHandler extends AsyncHttpResponseHandler {
private static final String LOG_TAG = "BinaryHttpResponseHandler";
private String[] mAllowedContentTypes = new String[]{
RequestParams.APPLICATION_OCTET_STREAM,
"image/jpeg",
"image/png",
"image/gif"
};
/**
* Method can be overriden to return allowed content types, can be sometimes better than passing
* data in constructor
*
* @return array of content-types or Pattern string templates (eg. '.*' to match every response)
*/
public String[] getAllowedContentTypes() {
return mAllowedContentTypes;
}
/**
* Creates a new BinaryHttpResponseHandler
*/
public BinaryHttpResponseHandler() {
super();
}
/**
* Creates a new BinaryHttpResponseHandler, and overrides the default allowed content types with
* passed String array (hopefully) of content types.
*
* @param allowedContentTypes content types array, eg. 'image/jpeg' or pattern '.*'
*/
public BinaryHttpResponseHandler(String[] allowedContentTypes) {
super();
if (allowedContentTypes != null) {
mAllowedContentTypes = allowedContentTypes;
} else {
Log.e(LOG_TAG, "Constructor passed allowedContentTypes was null !");
}
}
@Override
public abstract void onSuccess(int statusCode, Header[] headers, byte[] binaryData);
@Override
public abstract void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error);
@Override
public final void sendResponseMessage(HttpResponse response) throws IOException {
StatusLine status = response.getStatusLine();
Header[] contentTypeHeaders = response.getHeaders(AsyncHttpClient.HEADER_CONTENT_TYPE);
if (contentTypeHeaders.length != 1) {
//malformed/ambiguous HTTP Header, ABORT!
sendFailureMessage(
status.getStatusCode(),
response.getAllHeaders(),
null,
new HttpResponseException(
status.getStatusCode(),
"None, or more than one, Content-Type Header found!"
)
);
return;
}
Header contentTypeHeader = contentTypeHeaders[0];
boolean foundAllowedContentType = false;
for (String anAllowedContentType : getAllowedContentTypes()) {
try {
if (Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) {
foundAllowedContentType = true;
}
} catch (PatternSyntaxException e) {
Log.e("BinaryHttpResponseHandler", "Given pattern is not valid: " + anAllowedContentType, e);
}
}
if (!foundAllowedContentType) {
//Content-Type not in allowed list, ABORT!
sendFailureMessage(
status.getStatusCode(),
response.getAllHeaders(),
null,
new HttpResponseException(
status.getStatusCode(),
"Content-Type not allowed!"
)
);
return;
}
super.sendResponseMessage(response);
}
}
基类AsyncHttpResponseHandler的sendResponseMessage函数定义如下:
@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());
// 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);
}
}
}
}
[一般文件响应处理器类FileHttpResponseHandler]
FileHttpResponseHandler类的职责是将服务器返回的数据流保存到本地的文件中,可用于实现文件下载等功能,而且还能实时显示下载的进度。在构造函数中,我们可以指定保存的文件的具体路径,默认情况下是保存到系统的临时目录中。有如下三个重载的构造函数:
/**
* Obtains new FileAsyncHttpResponseHandler and stores response in passed file
*
* @param file File to store response within, must not be null
*/
public FileAsyncHttpResponseHandler(File file) {
this(file, false);
}
/**
* Obtains new FileAsyncHttpResponseHandler and stores response in passed file
*
* @param file File to store response within, must not be null
* @param append whether data should be appended to existing file
*/
public FileAsyncHttpResponseHandler(File file, boolean append) {
super();
AssertUtils.asserts(file != null, "File passed into FileAsyncHttpResponseHandler constructor must not be null");
this.mFile = file;
this.append = append;
}
/**
* Obtains new FileAsyncHttpResponseHandler against context with target being temporary file
*
* @param context Context, must not be null
*/
public FileAsyncHttpResponseHandler(Context context) {
super();
this.mFile = getTemporaryFile(context);
this.append = false;
}
/**
* Used when there is no file to be used when calling constructor
*
* @param context Context, must not be null
* @return temporary file or null if creating file failed
*/
protected File getTemporaryFile(Context context) {
AssertUtils.asserts(context != null, "Tried creating temporary file without having Context");
try {
// not effective in release mode
assert context != null;
return File.createTempFile("temp_", "_handled", context.getCacheDir());
} catch (IOException e) {
Log.e(LOG_TAG, "Cannot create temporary file", e);
}
return null;
}
可以看到,如果制定append参数为true,那么可以将数据内容拼接到现有的文件尾部。实现文件写入和进度消息发送的功能是通过覆写函数getResponseData实现的,该函数在父类AsyncHttpResponseHandler中被sendResponseMessage函数调用。
@Override
protected byte[] getResponseData(HttpEntity entity) throws IOException {
if (entity != null) {
InputStream instream = entity.getContent();
long contentLength = entity.getContentLength();
FileOutputStream buffer = new FileOutputStream(getTargetFile(), this.append);
if (instream != null) {
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.write(tmp, 0, l);
sendProgressMessage(count, (int) contentLength);
}
} finally {
AsyncHttpClient.silentCloseInputStream(instream);
buffer.flush();
AsyncHttpClient.silentCloseOutputStream(buffer);
}
}
}
return null;
}
[文件断点续传响应处理器类RangeFileHttpResponseHandler]
Http请求中涉及到断点续传功能时,一般需要在Http请求头部设置Range字段,指定第一个字节偏移量和最后一个字节偏移量(偏移量从0开始),格式如下:Range: bytes=0-100;当服务器支持断点续传功能时,会在Http响应头部中包含Content-Range字段,格式如下:Content-Range: bytes 0-100/2350,其中2350表示文件总的大小。如果Range头部指定的偏移量超出文件总的大小,那么服务器会返回Http 416错误码,表示所请求的范围无法满足;如果Http请求头部保护Range字段,那么服务器响应成功后会返回Http 206错误码,表示Partial Content,客户端发送了一个带有Range字段的GET请求,服务器端完成了它。RangeFileHttpResponseHandler完整代码如下:
public abstract class RangeFileAsyncHttpResponseHandler extends FileAsyncHttpResponseHandler {
private static final String LOG_TAG = "RangeFileAsyncHttpResponseHandler";
private long current = 0;
private boolean append = false;
/**
* Obtains new RangeFileAsyncHttpResponseHandler and stores response in passed file
*
* @param file File to store response within, must not be null
*/
public RangeFileAsyncHttpResponseHandler(File file) {
super(file);
}
@Override
public void sendResponseMessage(HttpResponse response) throws IOException {
if (!Thread.currentThread().isInterrupted()) {
StatusLine status = response.getStatusLine();
if (status.getStatusCode() == HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE) {
//already finished
if (!Thread.currentThread().isInterrupted())
sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), null);
} else if (status.getStatusCode() >= 300) {
if (!Thread.currentThread().isInterrupted())
sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), null, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()));
} else {
if (!Thread.currentThread().isInterrupted()) {
Header header = response.getFirstHeader(AsyncHttpClient.HEADER_CONTENT_RANGE);
if (header == null) {
append = false;
current = 0;
} else {
Log.v(LOG_TAG, AsyncHttpClient.HEADER_CONTENT_RANGE + ": " + header.getValue());
}
sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), getResponseData(response.getEntity()));
}
}
}
}
@Override
protected byte[] getResponseData(HttpEntity entity) throws IOException {
if (entity != null) {
InputStream instream = entity.getContent();
long contentLength = entity.getContentLength() + current;
FileOutputStream buffer = new FileOutputStream(getTargetFile(), append);
if (instream != null) {
try {
byte[] tmp = new byte[BUFFER_SIZE];
int l;
while (current < contentLength && (l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) {
current += l;
buffer.write(tmp, 0, l);
sendProgressMessage((int) current, (int) contentLength);
}
} finally {
instream.close();
buffer.flush();
buffer.close();
}
}
}
return null;
}
public void updateRequestHeaders(HttpUriRequest uriRequest) {
if (mFile.exists() && mFile.canWrite())
current = mFile.length();
if (current > 0) {
append = true;
uriRequest.setHeader("Range", "bytes=" + current + "-");
}
}
}
[xml格式响应处理器抽象类SaxAsyncHttpResponseHandler]
xml格式常见的两种解析方法分别是SAX和DOM,SAX是基于事件流的解析,而DOM是基于XML文档树结构的解析。由于DOM方式是将整个xml文件load进内存,并构建一个驻留内存的树结构,因此,在手机这种内存紧张的嵌入式设备中一般不使用这种方式解析XML文档,而是选用SAX。SaxAsyncHttpResponseHandler是一个抽象模板类,模板实例化时可以指定具体的DefaultHandler子类作为内容处理器,整个处理过程就是将服务器端返回的数据根据xml格式进行解析,关键代码位于getResponseData函数中,完整代码如下:
public abstract class SaxAsyncHttpResponseHandler<T extends DefaultHandler> extends AsyncHttpResponseHandler {
/**
* Generic Type of handler
*/
private T handler = null;
private final static String LOG_TAG = "SaxAsyncHttpResponseHandler";
/**
* Constructs new SaxAsyncHttpResponseHandler with given handler instance
*
* @param t instance of Handler extending DefaultHandler
* @see org.xml.sax.helpers.DefaultHandler
*/
public SaxAsyncHttpResponseHandler(T t) {
super();
if (t == null) {
throw new Error("null instance of <T extends DefaultHandler> passed to constructor");
}
this.handler = t;
}
/**
* Deconstructs response into given content handler
*
* @param entity returned HttpEntity
* @return deconstructed response
* @throws java.io.IOException
* @see org.apache.http.HttpEntity
*/
@Override
protected byte[] getResponseData(HttpEntity entity) throws IOException {
if (entity != null) {
InputStream instream = entity.getContent();
InputStreamReader inputStreamReader = null;
if (instream != null) {
try {
SAXParserFactory sfactory = SAXParserFactory.newInstance();
SAXParser sparser = sfactory.newSAXParser();
XMLReader rssReader = sparser.getXMLReader();
rssReader.setContentHandler(handler);
inputStreamReader = new InputStreamReader(instream, DEFAULT_CHARSET);
rssReader.parse(new InputSource(inputStreamReader));
} catch (SAXException e) {
Log.e(LOG_TAG, "getResponseData exception", e);
} catch (ParserConfigurationException e) {
Log.e(LOG_TAG, "getResponseData exception", e);
} finally {
AsyncHttpClient.silentCloseInputStream(instream);
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) { /*ignore*/ }
}
}
}
}
return null;
}
/**
* Default onSuccess method for this AsyncHttpResponseHandler to override
*
* @param statusCode returned HTTP status code
* @param headers returned HTTP headers
* @param t instance of Handler extending DefaultHandler
*/
public abstract void onSuccess(int statusCode, Header[] headers, T t);
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
onSuccess(statusCode, headers, handler);
}
/**
* Default onFailure method for this AsyncHttpResponseHandler to override
*
* @param statusCode returned HTTP status code
* @param headers returned HTTP headers
* @param t instance of Handler extending DefaultHandler
*/
public abstract void onFailure(int statusCode, Header[] headers, T t);
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
onSuccess(statusCode, headers, handler);
}
}