android-async-http 源码原理解析
1. 功能介绍
功能介绍
android-async-http 是一个基于回调的并基于 Apache's httpClient libraries 为 Android 开发的一个框架。 所有的请求在非 UI 线程中处理,而回调是在 Handler 里面处理。你也可以在 Service 或者后台线程使用, lib 会自动识别 并在 context 里处理。
具体使用方法可见 http://loopj.com/android-async-http/
概念
Http 请求 (AsyncHttpRequest):又可以成为 Http 请求的单线程对象,通过将该单线程模型 submit 至 线程池中统一管理。
Http 响应处理者(HttpResponseHandler): 可以针对性的生成不同数据类型的相应处理者,包含 Json, String, binary, file ... 的处理操作。
重试处理者(RetryHandler):可以针对不同的异常情况根据自身预设定的 white&black exception List 进行智能筛选,选择需要重试的请求。
拦截器(Interceptor): 在该框架中的应用类似于 Spring 中的 aop(Aspect Oriented Programming), 多次设定于 Httpclient 属性中。
特点:
- 异步发送 HTTP 请求,在回调函数中处理响应
- HTTP 请求过程不在 UI 线程进行
- 使用线程池来管理并发数
- 支持 GET/POST 请求参数单独设置
- 无需其他库上传序列化 JSON 数据
- 处理重定向
- 体积小,只有 90K
- 针对不同的网络连接对重试次数进行智能优化
- 支持 gzip
- 二进制通信协议使用 BinaryHttpResponseHandler 处理
- 内置 Json 解析,使用 JsonHttpResponseHandler 对响应进行处理
- 使用 FileAsyncHttpResponseHandler 直接将响应保存到文件中
- 动态保存 Cookie,将 Cookie 保存到应用的 SharedPreferences 中
- 使用 BaseJsonHttpResponseHandler 可以搭配 Jackson JSON,Gson 或者其他的 Json 反序列化库
- 支持 SAX 解析,使用 SaxAsyncHttpResponseHandler
- 支持多语言多种编码方式,不只是 UTF-8
优点:
个人觉得优点就是
- 该框架是基于回调的,各种回调类型的处理都有
- 直接放到主线程使用匿名调用即可
- 可以扩展自己的回调处理
- 支持处理重定向,文档的续传
- 可以保存 Cookie, 可以添加请求凭证保存,单次输入,多次使用
- 根据常用网络请求异常进行黑白名单查询,针对性重试机构
- 整个请求处理是使用 gzip 的,节省流量,速度快 用起来方便。
2. 总体设计
总体设计图
图 2-1 总体设计图
以上是 android-async-http 的总体设计图。
- 整个请求子线程 AsyncHttpRequest 处于的 参数封装是在主类 AsyncHttpClient 中进行的。 处理完成之后将该请求线程 提交至 threadPool 中由线程池调度完成。
- 请求的处理是在子线程 AsyncHttpRequest 中处理的,其中包含了 ResponseHandlerInterface 的响应回调处理大接口, 也处理了由于各种原因造成的访问失败的智能重试。
- 请求的响应实现 ResponseHandlerInterface 接口的 AsyncHttpResponseHandler 类,类似于 BaseAdapter 模式,基本上使用 Handler 消息处理了数据处理的各种情况。 其中 AsyncHttpResponseHandler 的各种子类负责了对应各种数据类型的解析与处理。
3. 流程图
3.1. 总体流程图
图 3-1 总体流程图
3.2. 请求线程流程图
图 3-2 请求线程流程图
3.3. makeRequestWithRetries 函数模块调用
图 3-3 请求函数模块调用
3.4. makeRequest 函数模块调用
图 3-4 请求函数模块调用
3.5 整体回调顺序
图 3-5 请求函数整体回调顺序
4. 详细设计
4.1 总类关系图
图 4-1
4.2 核心类功能介绍
4.2.1 类详细介绍(未完成)
- ResponseHandlerInterface 整个框架中网络访问返回数据的处理接口,集合了包括消息发送,数据处理回调在内的共 16 个函数。
- AsyncHttpClient.class 整个框架的调用集合类与各个参数的组装调用类。
- AsyncHttpRequest.class 网络访问子线程类
- AsyncHttpResponseHandler.class 网络响应处理主类
4.1 AsyncHttpResponseHandler 类图
图 4.1-1 AsyncHttpResponseHandler 类图
4.2 成员变量
private Handler handler; /// 内部类 ResponderHandler 的赋值对象, 负责发送,处理消息。
private boolean useSynchronousMode;
private boolean usePoolThread;
private URI requestURI = null;
private Header[] requestHeaders = null; /// 请求头信息
private Looper looper = null; /// 默认如果 looper 为 null,则赋值为 Looper.myLooper() 也就是获取的主线程中的 Looper ;
4.3 重要函数
// Methods which emulate android's Handler and Message methods
protected void handleMessage(Message message) {
Object[] response;
try {
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((Long) response[0], (Long) 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;
}
} catch(Throwable error) {
onUserException(error);
}
}
分析:
- 接收处理 请求访问以及返回过程中的所有状态, 包含 SUCCESS_MESSAGE, FAILURE_MESSAGE, START_MESSAGE, FINISH_MESSAGE, PROGRESS_MESSAGE,RETRY_MESSAGE, CANCEL_MESSAGE 在内的各种情况,以及回调。
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 > 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];
long count = 0;
int l;
// 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, (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;
}
分析:
- sendMessage 中包含了 所有 的发送消息通用函数, 调用 handler 的消息发送函数,send Or post 至 ResponderHandler 内部, ResponderHandler 内部调用 AsyncHttpResponseHandler 内部 handleMessage 函数,来处理各种情况。
protected void sendMessage(Message msg) ;
protected void postRunnable(Runnable runnable);
- 接收返回 HttpEntity 实体内部 content , 并按照 BufferSize 来读取数据, append 至 ByteArrayBuffer 内部, 同时发布进度状态 sendProgressMessage ,回调 onProgress 函数。 最后返回 ByteArrayBuffer 的 byte[] .
NOTE: 其中 ByteArrayBuffer 属于自增长型的缓冲数据类型, code 中 的默认大小为 4096 也就是 1KB 大小 , 而 ByteArrayBuffer 过大也会导致 OutOfMemoryError , 超过 VM heapsize 的分配内存之后就会报内存溢出异常。
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalArgumentException("HTTP entity too large to be buffered in memory");
}
后边 Int 的数据类型范围: -2147483648~2147483647 最大值 2147483647 = 2^10 也就是 1G , 上边 code 写的, 也就是最大不能超 1GB , 这样的话但是后边的强转之后直接将 int 设置到 ByteArrayBuffer 中, 显然作者是将这个问题交给了系统自动去检测了。
Runtime rt = Runtime.getRuntime();
long maxMemory = rt.maxMemory();
Log.v("onCreate", "maxMemory:" + Long.toString(maxMemory));
个人认为,由于每一种机型的型号不同,系统分配的 HeapSize 也不同, 可以根据这个来进行匹配,检测。
链接见:
http://stackoverflow.com/questions/2630158/detect-application-heap-size-in-android/9428660#9428660
RequestParams 请求参数的组装,实现 Serializable 接口。
RetryHandler.class 重试处理类
6.1 成员变量
黑白名单列表:
HashSet<Class<?>> exceptionWhitelist = new HashSet<Class<?>>();
HashSet<Class<?>> exceptionBlacklist = new HashSet<Class<?>>();
默认黑白名单处理策略:
static {
// Retry if the server dropped connection on us
exceptionWhitelist.add(NoHttpResponseException.class);
// retry-this, since it may happens as part of a Wi-Fi to 3G failover
exceptionWhitelist.add(UnknownHostException.class);
// retry-this, since it may happens as part of a Wi-Fi to 3G failover
exceptionWhitelist.add(SocketException.class);
// never retry timeouts
exceptionBlacklist.add(InterruptedIOException.class);
// never retry SSL handshake failures
exceptionBlacklist.add(SSLException.class);
}
6.2 重要函数
public boolean retryRequest(IOException exception, int executionCount, HttpContext context){
boolean retry = true;
Boolean b = (Boolean) context.getAttribute(ExecutionContext.HTTP_REQ_SENT);
boolean sent = (b != null && b);
if (executionCount > maxRetries) {
// Do not retry if over max retry count
retry = false;
} else if (isInList(exceptionWhitelist, exception)) {
// immediately retry if error is whitelisted
retry = true;
} else if (isInList(exceptionBlacklist, exception)) {
// immediately cancel retry if the error is blacklisted
retry = false;
} else if (!sent) {
// for most other errors, retry only if request hasn't been fully sent yet
retry = true;
}
if (retry) {
// resend all idempotent requests
HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
if (currentReq == null) {
return false;
}
}
if (retry) {
SystemClock.sleep(retrySleepTimeMS);
} else {
exception.printStackTrace();
}
return retry;
}
分析:
整体异常处理策略就是,根据参数 IOException 的类型,在对应的黑白名单列表中进行轮询操作,如果发现匹配项目,则进行相应的操作, 例如: 如果异常出现在白名单列表中,也就是在网络访问中常发生的,不可避免的,由用户网络环境造成的异常, 则返回可以重试标识。反之,则结束重试。
6.3 其他函数
添加黑白名单的方法,轮询方法等。
addClassToWhitelist(Class<?> cls);
addClassToBlacklist(Class<?> cls);
boolean isInList(HashSet<Class<?>> list, Throwable error);
- RequestHandle 请求句柄类,例如 请求任务取消,是否完成,垃圾回收操作, 也挺重要的。
7.1 成员变量
这个类貌似只有这么一个成员变量 就是 AsyncHttpRequest 的弱引用, 看来作者对于内存溢出考虑也挺周全。
private final WeakReference<AsyncHttpRequest> request;
7.2 重要函数
public boolean cancel(final boolean mayInterruptIfRunning) {
final AsyncHttpRequest _request = request.get();
if (_request != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
new Thread(new Runnable() {
@Override
public void run() {
_request.cancel(mayInterruptIfRunning);
}
}).start();
} else {
_request.cancel(mayInterruptIfRunning);
}
}
return false;
}
分析:
取消请求,直接将 AsyncHttpRequest 内部的 iscancel 置为 false, 调用 HttpUriRequest 的 abort 函数, 中断请求,发送任务取消 Notification。
NOTE : 这里我发现 mayInterruptIfRunning 参数木有用到,我找了半天没有找到。
7.3 其他函数
public boolean isFinished() ; // 判断是否请求结束。
public boolean isCancelled() ; // 判断任务是否已经取消过了。
public boolean shouldBeGarbageCollected() // 这货好像是这个版本才出现的, 作用是 告诉 gc 要准备收回该对象占用内存
SyncHttpClient.class 异步网络请求类
PersistentCookieStore 本地的 cookie 的存储类
9.1 成员变量
private final ConcurrentHashMap<String, Cookie> cookies; /// 运行时维护的一个 cookie 的 Map
private final SharedPreferences cookiePrefs; /// cookie 保存在了 sharedPreference 中
9.2 成员函数
public void addCookie(Cookie cookie) ;/// 添加 cookie
public void clear() ; /// 清除 cookie
public boolean clearExpired(Date date); // 清除过期的 cookie
public void deleteCookie(Cookie cookie); // 删除指定名称的 cookie 从 sharedPreference
protected Cookie decodeCookie(String cookieString); // 将 cookie 以 16 进制编码的方式通过 SerializableCookie readObject(ObjectInputStream in) 函数添加至 Cookie 中,返回 Cookie
protected String encodeCookie(SerializableCookie cookie); // 将 Cookie 对象序列化至 String 中
DataAsyncHttpResponseHandler 数据的异步下载处理
FileAsyncHttpResponseHandler 文件的异步下载处理
14.1 成员变量
protected final File mFile; /// 临时文档的操作对象。
protected final boolean append; /// 文档操作对象是否在上一次写的基础上追加数据。
14.2 重要函数
@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, contentLength);
}
} finally {
AsyncHttpClient.silentCloseInputStream(instream);
buffer.flush();
AsyncHttpClient.silentCloseOutputStream(buffer);
}
}
}
return null;
}
分析:
该方法是处理接收的 HttpEntiry 的 Content 内容,使用流写至一个临时的 file, 同时使用父类 AsyncHttpResponseHandler 的 Handler 发送进度, 最后关闭流。
14.3 其他函数
public boolean deleteTargetFile() ;// 删除目标文件;
protected File getTemporaryFile(Context context);// 获取临时创建的文件;
以及其他父类 AsyncHttpResponseHandler 中含有的状态回调函数。
BinaryHttpResponseHandler 二进制数据的下载处理
TextHttpResponseHandler 文本数据的加载处理
16.1 成员变量
private String responseCharset = DEFAULT_CHARSET;/// 相应数据编码,默认 UTF-8
16.2 成员函数
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[] , 通过使用父类 AsyncHttpResponseHandler 的 Handler 中 onSuccess 方法回传的 byte[] ,添加指定的 charset(默认编码 UTF-8),返回从第一个字符开始截取的 string 对象。
NOTE:中间有个常量 UTF8_BOM,内容是 发现 BOM 的作用是识别 UTF-8 编码的作用,具体请参考链接: utf-8 与 utf-8(无 BOM) 的区别
JsonHttpResponseHandler Json 数据的加载解析处理
BaseJsonHttpResponseHandler 基础 Json 数据的解析,继承自 TextHttpResponseHandler
18.1 成员变量
无
18.2 重要函数
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() && !getUsePoolThread()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
onSuccess(statusCode, headers, null, null);
}
}
分析:
使用 TextHttpResponseHandler 中 onSuccess 的 Text 返回方法, 在返回 String 内容不为 null 的情况下, 直接解析 responseString, 并调用抽象函数 parseResponse ,解析的过程在子线程中运行,这取决于用户的是操作方式,是异步还是同步。
18.3 其他函数
protected abstract JSON_TYPE parseResponse(String rawJsonData, boolean isFailure); // 需要子类来实现具体的解析方式.
JsonStreamerEntity Json 流的处理,该类适合 Json base 64 编码的上传操作, 节省内存,可以适合大文件。
SaxAsyncHttpResponseHandler xml 的解析处理,继承自 AsyncHttpResponseHandler 使用的 sax 方法解析。
PreemptiveAuthorizationHttpRequestInterceptor 预验证拦截器类
MyRedirectHandler 重定向处理器类, 源码中标识该类引用自 stackoverflow https://stackoverflow.com/questions/3420767/httpclient-redirecting-to-url-with-spaces-throwing-exception
Base64 二进制数据的 Base 64 的编码与解码
Base64DataException Base 64 编码的操作异常类
Base64OutputStream Base 64 输出流操作类
HttpDelete HttpDelete 操作类,继承自 HttpEntityEnclosingRequestBase 类
HttpGet HttpGet 操作类,继承自 HttpEntityEnclosingRequestBase 类
HttpPatch HttpPatch 操作类,继承自 HttpEntityEnclosingRequestBase 类
JsonValueInterface 接口,可以中 App 来封装完整的封装 JSON 的值
SerializableCookie 配角, 在 PersistentCookieStore 类中序列化 Cookie 的值时使用
SimpleMultipartEntity 简单的多部分实体,主要用于发送一个或多个文档
Utils 工具类
5. 杂谈总结
第一次大胆分析大神的源码,整个分析过程一共使用了三天 8 月 6 号就完成了,只是一直忙于工作没有上传,十分抱歉,文章中包括与设计图的制作,中间还 有一些部分不太了解,有很多不对的地方,请大家批评指正 。框架整个设计框架巧妙的使用了基于 Apache HttpClient 框架的回调。充分使用了 Httpclient 中的各种 handler , interceptor 将整个数据响应过程通过接口使其可扩展化,过程化,结构化。 而框架整体看起来又浑然一体,的确很牛气。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论