第一次写源码解读这种文章,不专业还请多指教哈、
先介绍一下android-async-http-master。这个是安卓上的一个开源项目 ,这个网络请求库是基于Apache HttpClient库之上的一个异步网络请求处理库,网络处理均基于Android的非UI线程,通过回调方法处理请求结果。
其主要特征如下:
处理异步Http请求,并通过匿名内部类处理回调结果
Http请求均位于非UI线程,不会阻塞UI操作
通过线程池处理并发请求
处理文件上传、下载
响应结果自动打包JSON格式
自动处理连接断开时请求重连
使用方法:
先将项目内年jar包引用到工程中。然后将项目 中com.loopj.android.http放到工程中,然后就可以使用了。一开始的时候不知道。还是弄了一会儿才发现还得copy一个包进来 。
工程调试好之后运行工程。这个开源项目 自带了一个如何调用接口的一个demo。
效果如图
里面包括了在开发中常用的几种网络请求类型。
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Class<?> targetClass;
switch (position) {
case 0:
default:
targetClass = GetSample.class;
break;
case 1:
targetClass = PostSample.class;
break;
case 2:
targetClass = DeleteSample.class;
break;
case 3:
targetClass = PutSample.class;
break;
case 4:
targetClass = JsonSample.class;
break;
case 5:
targetClass = FileSample.class;
break;
case 6:
targetClass = BinarySample.class;
break;
case 7:
targetClass = ThreadingTimeoutSample.class;
break;
case 8:
targetClass = CancelAllRequestsSample.class;
break;
case 9:
targetClass = CancelRequestHandleSample.class;
break;
case 10:
targetClass = SynchronousClientSample.class;
break;
}
if (targetClass != null)
startActivity(new Intent(this, targetClass));
}
在主界面中通过实现onListItemClick()方法调用各个示例类。这个编码风格很好啊。
先看最常用的get例子
执行一下get操作
getsample代码如下
package com.loopj.android.http.sample;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import android.util.Log;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestHandle;
import com.loopj.android.http.ResponseHandlerInterface;
public class GetSample extends SampleParentActivity {
private static final String LOG_TAG = "GetSample";
@Override
public RequestHandle executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, ResponseHandlerInterface responseHandler) {
return client.get(this, URL, headers, null, responseHandler);
}
@Override
public int getSampleTitle() {
return R.string.title_get_sample;
}
@Override
public boolean isRequestBodyAllowed() {
return false;
}
@Override
public boolean isRequestHeadersAllowed() {
return true;
}
@Override
public String getDefaultURL() {
return "https://httpbin.org/get";
}
@Override
public ResponseHandlerInterface getResponseHandler() {
return new AsyncHttpResponseHandler() {
@Override
public void onStart() {
clearOutputs();
}
@Override
public void onSuccess(int statusCode, Header[] headers,
byte[] response) {
debugHeaders(LOG_TAG, headers);
debugStatusCode(LOG_TAG, statusCode);
debugResponse(LOG_TAG, new String(response));
}
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] errorResponse, Throwable e) {
debugHeaders(LOG_TAG, headers);
debugStatusCode(LOG_TAG, statusCode);
debugThrowable(LOG_TAG, e);
if (errorResponse != null) {
debugResponse(LOG_TAG, new String(errorResponse));
}
}
};
}
}
还是比较简短的。他继承了SampleParentActivity。在SampleParentActivity里已经为主界面设置了主要布局和监听。所以这使得getsample代码很简短。以下是
SampleParentActivity中的主要代码和调用步骤。
public void onRunButtonPressed() {
addRequestHandle(executeSample(getAsyncHttpClient(),
(urlEditText == null || urlEditText.getText() == null) ? getDefaultURL() : urlEditText.getText().toString(),
getRequestHeaders(),
getRequestEntity(),
getResponseHandler()));
}
在
SampleParentActivity里已经为run这个button添加了监听,点击后执行上面这个方法。其中addRequestHandle()使子类可以通过重载executeSample()方法而实现不同的功能 。
@Override
public void addRequestHandle(RequestHandle handle) {
if (null != handle) {
requestHandles.add(handle);
}
}
然后是getAsyncHttpClient()方法,得到异步的http链接。是这个开源项目中的核心部分了。在
SampleParentActivity类里一开头就声明好了这一个属性。
再看getsample里实现的这个executeSample方法
@Override
public RequestHandle executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, ResponseHandlerInterface responseHandler) {
return client.get(this, URL, headers, null, responseHandler);
}
主要就是调用AsyncHttpClient类内封装的方法完成操作。
下面主要分析一下AsyncHttpClient类。这个类有1159行代码。所以复制上来不太便于分析,这里根据get的调用及执行步骤进行分析 。
当getSamlpe执行AsyncHttpClient的get方法时。调用如下代码。
/**
* Perform a HTTP GET request and track the Android Context which initiated the request with
* customized headers
*
* @param context Context to execute request against
* @param url the URL to send the request to.
* @param headers set headers only for this request
* @param params additional GET parameters to send with the request.
* @param responseHandler the response handler instance that should handle the response.
* @return RequestHandle of future request process
*/
public RequestHandle get(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) {
HttpUriRequest request = new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params));
if (headers != null) request.setHeaders(headers);
return sendRequest(httpClient, httpContext, request, null, responseHandler,
context);
}
Context参数是上下文,这里说一下,android系统中的activity中继承于Context的,所以我们经常在Context参数中传入XXXactivity.this。实际上就是传入本activity的超类。
url 就不用说了,是请求的地址。在这里是"https://httpbin.org/get";
headers
HTTP是“Hypertext Transfer Protocol”的所写,整个万维网都在使用这种协议,几乎你在浏览器里看到的大部分内容都是通过http协议来传输的,比如这篇文章。
HTTP Headers是HTTP请求和相应的核心,它承载了关于客户端浏览器,请求页面,服务器等相关的信息。
示例
当你在浏览器地址栏里键入一个url,你的浏览器将会类似如下的http请求:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
Host: net.tutsplus.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
Pragma: no-cache
Cache-Control: no-cache
HttpEntity 翻译过来是http实体,我的理解是http请求的主要内容,如我们如果是post操作那么这个实体内应该是需要post的内容,如果是get操作,那这个实就是服务器内返回过来的数据。所以在getSample里传入为null
ResponseHandlerInterface 这个是项目中自定义的一个handler用于编写在请求开始 成功 失败,三种情况后需要执行的操作。
继续看。在 AsyncHttpClient的get方法中httpget的构造参数执行了getUrlWithQueryString()方法,这个方法将返回一个String 类型的uri.主要是将参数进行整合最后返回一个带请求参数的uri。下面这个方法的代码
/**
* Will encode url, if not disabled, and adds params on the end of it
*
* @param url String with URL, should be valid URL without params
* @param params RequestParams to be appended on the end of URL
* @param shouldEncodeUrl whether url should be encoded (replaces spaces with %20)
* @return encoded url if requested with params appended if any available
*/
public static String getUrlWithQueryString(boolean shouldEncodeUrl, String url, RequestParams params) {
if (shouldEncodeUrl)
url = url.replace(" ", "%20");
if (params != null) {
// Construct the query string and trim it, in case it
// includes any excessive white spaces.
String paramString = params.getParamString().trim();
// Only add the query string if it isn't empty and it
// isn't equal to '?'.
if (!paramString.equals("") && !paramString.equals("?")) {
url += url.contains("?") ? "&" : "?";
url += paramString;
}
}
return url;
}
第一个参数 是否转化字符编码把空格替换成%20, 有时候在网址中经常发现原来的空格会被变成%20就是这个道理。
然后参数会被以?参数名=值添加在url后面。RequestParams类型本是一个map表,这里执行了一个getParamString方法将map表转化成字符串并去了空格,根据深度优先的思路进行分析。
protected String getParamString() {
return URLEncodedUtils.format(getParamsList(), contentEncoding);
}
第二个参数 是默认编码类型 初值为ut8
protected String contentEncoding = HTTP.UTF_8;
那么再看getParamsList();
protected List<BasicNameValuePair> getParamsList() {
List<BasicNameValuePair> lparams = new LinkedList();
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
lparams.addAll(getParamsList(null, urlParamsWithObjects));
return lparams;
}
返回值 是一个链表,类型是基本键值对。是http.message的一个类型。当将所有参数以键值对的方式放在链表里之后又执行了一个addall方法 。由于 这个不常用 所以还是看了一个代码
/**
* Adds the objects in the specified Collection to this {@code LinkedList}.
*
* @param collection
* the collection of objects.
* @return {@code true} if this {@code LinkedList} is modified,
* {@code false} otherwise.
*/
@Override
public boolean addAll(Collection<? extends E> collection) {
int adding = collection.size();
if (adding == 0) {
return false;
}
Collection<? extends E> elements = (collection == this) ?
new ArrayList<E>(collection) : collection;
Link<E> previous = voidLink.previous;
for (E e : elements) {
Link<E> newLink = new Link<E>(e, previous, null);
previous.next = newLink;
previous = newLink;
}
previous.next = voidLink;
voidLink.previous = previous;
size += adding;
modCount++;
return true;
}
看完就明白了,是将一个集合加在了整个链表上。
那么再看getParamsList方法 中的getParamsList(String key, Object value)方法
private List<BasicNameValuePair> getParamsList(String key, Object value) {
List<BasicNameValuePair> params = new LinkedList();
if (value instanceof Map) {
Map map = (Map) value;
List list = new ArrayList<Object>(map.keySet());
// Ensure consistent ordering in query string
Collections.sort(list);
for (Object nestedKey : list) {
if (nestedKey instanceof String) {
Object nestedValue = map.get(nestedKey);
if (nestedValue != null) {
params.addAll(getParamsList(key == null ? (String) nestedKey : String.format("%s[%s]", key, nestedKey),
nestedValue));
}
}
}
} else if (value instanceof List) {
List list = (List) value;
for (Object nestedValue : list) {
params.addAll(getParamsList(String.format("%s[]", key), nestedValue));
}
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (Object nestedValue : array) {
params.addAll(getParamsList(String.format("%s[]", key), nestedValue));
}
} else if (value instanceof Set) {
Set set = (Set) value;
for (Object nestedValue : set) {
params.addAll(getParamsList(key, nestedValue));
}
} else if (value instanceof String) {
params.add(new BasicNameValuePair(key, (String) value));
}
return params;
}
这里看到其实 是根据value参数的类型提供了不同的构造 List<BasicNameValuePair> 返回值的方法
那么在getParamsList(String key, Object value)的参数urlParamsWithObjects 就是传说中httpentity的数据承载了吧。
protected final ConcurrentHashMap<String, String> urlParams = new ConcurrentHashMap();
protected final ConcurrentHashMap<String, StreamWrapper> streamParams = new ConcurrentHashMap();
protected final ConcurrentHashMap<String, FileWrapper> fileParams = new ConcurrentHashMap();
protected final ConcurrentHashMap<String, Object> urlParamsWithObjects = new ConcurrentHashMap();
我们看到这个RequestParams类内有四种这样的hashmap用于放数据的变量 。所以它应该是根据不同的参数类型选择不同的数据载体
之前的return URLEncodedUtils.format(getParamsList(), contentEncoding);
是对整个字符串进行编码转换工作,转换成了utf8
那么分析完毕。回到AsyncHttpClient类的get方法
public RequestHandle get(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) {
HttpUriRequest request = new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params));
if (headers != null) request.setHeaders(headers);
return sendRequest(httpClient, httpContext, request, null, responseHandler,
context);
}
设置了headers之后执行发送请求
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("Content-Type", contentType);
}
responseHandler.setRequestHeaders(uriRequest.getAllHeaders());
responseHandler.setRequestURI(uriRequest.getURI());
AsyncHttpRequest request = new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler);
threadPool.submit(request);
RequestHandle requestHandle = new RequestHandle(request);
if (context != null) {
// Add request to request map
List<RequestHandle> requestList = requestMap.get(context);
if (requestList == null) {
requestList = new LinkedList();
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;
}
又是好长一段前面包括的是各种错误处理、
在这个方法 中看到了
public class AsyncHttpRequest implements Runnable
这个类实现 了runnable接口,这就是为什么这个开源项目可以执行异步请求并且可以控制取消了
@Override
public void run() {
if (isCancelled()) {
return;
}
if (responseHandler != null) {
responseHandler.sendStartMessage();
}
if (isCancelled()) {
return;
}
try {
makeRequestWithRetries();
} 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()) {
return;
}
if (responseHandler != null) {
responseHandler.sendFinishMessage();
}
isFinished = true;
}
在run方法 内我们看到。如果responseHandler不为null那么就向它发送开始消息 ,我们之前看到过这个项目可以对开始 成功 失败三个状态执行不同的自定义操作就是通过它来实现的。之后 开始发送请求
在请求开始之前和开始之后 各有一个取消的检查,这就是说如果在请求之前取消请求将不会被执行,为了减少操作。在请求之后取消将不会发送finish消息 ,避免执行我们自定义 的finish方法
在用AsyncHttpRequest声明好runnable对象之后 提交到线程池里
threadPool.submit(request);
这个线程池应该在AsyncHttpClient的构造函数里进行了初值的设置
/**
* Creates a new AsyncHttpClient with default constructor arguments values
*/
public AsyncHttpClient() {
this(false, 80, 443);
}
这是他的无参构造函数,也就是我们最常用的那个。、
将调 用第二个构造 函数
public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
this(getDefaultSchemeRegistry(fixNoHttpResponseException, httpPort, httpsPort));
}
getDefaultSchemeRegistry方法用于获得默认的协议模式。比如“http”或“https”同时包含一些协议属性,比如默认端口,用来为给定协议创建java.net.Socket实例的套接字工厂。SchemeRegistry类用来维持一组Scheme,当去通过请求URI建立连接时,HttpClient可以从中选择:
private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
if (fixNoHttpResponseException) {
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)
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;
}
第一个布尔型参数设置了是否验证其ssl证书。ssl是一种加密协议,
第二个参数 是http协议的端口
第三个参数 是https使用的端口
然后又将调用Asynchttpclient的第三个构造参数
public AsyncHttpClient(SchemeRegistry schemeRegistry) {
BasicHttpParams httpParams = new BasicHttpParams();
ConnManagerParams.setTimeout(httpParams, timeout);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections));
ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
HttpConnectionParams.setSoTimeout(httpParams, timeout);
HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setUserAgent(httpParams, String.format("android-async-http/%s (http://loopj.com/android-async-http)", VERSION));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
threadPool = Executors.newCachedThreadPool();
requestMap = new WeakHashMap();
clientHeaderMap = new HashMap();
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())
);
}
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);
httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_SLEEP_TIME_MILLIS));
}
在设置了一些基本项之后 我们看到了线程池
threadPool = Executors.newCachedThreadPool();
这个线程池我在之前介绍过 这里再说一遍
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
至此get方法的执行过程就到这儿。好复杂的已经,有的函数没有分析到是因为是封装在jar包内的,看不了。