黑马程序员技术交流社区
标题: 【框架解读】android-async-http-master 源码解读 [打印本页]
作者: Android_Robot 时间: 2016-5-5 09:45
标题: 【框架解读】android-async-http-master 源码解读
本帖最后由 Android_Robot 于 2016-10-26 15:16 编辑
先介绍一下android-async-http-master。这个是安卓上的一个开源项目 ,这个网络请求库是基于Apache HttpClient库之上的一个异步网络请求处理库,网络处理均基于Android的非UI线程,通过回调方法处理请求结果。
使用方法:
先将项目内年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代码如下
- <span style="line-height: 1.5;">package com.loopj.android.http.sample; </span>
- 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。下面这个方法的代码
- <span style="line-height: 1.5;"> </span><span style="line-height: 1.5;">/**</span>
复制代码 第一个参数 是否转化字符编码把空格替换成%20, 有时候在网址中经常发现原来的空格会被变成%20就是这个道理。
然后参数会被以?参数名=值添加在url后面。RequestParams类型本是一个map表,这里执行了一个getParamString方法将map表转化成字符串并去了空格,根据深度优先的思路进行分析。 - <span style="line-height: 1.5;">protected String getParamString() {</span><span style="line-height: 1.5;"> </span><span style="line-height: 1.5;"> </span>
复制代码 第二个参数 是默认编码类型 初值为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方法
- <font size="2">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);
- } </font>
复制代码 设置了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接口,这就是为什么这个开源项目可以执行异步请求并且可以控制取消了
- <font size="2">@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();
- } </font>
- <font size="2">
- isFinished = true;
- } </font>
复制代码 在run方法 内我们看到。如果responseHandler不为null那么就向它发送开始消息 ,我们之前看到过这个项目可以对开始 成功 失败三个状态执行不同的自定义操作就是通过它来实现的。之后 开始发送请求
在请求开始之前和开始之后 各有一个取消的检查,这就是说如果在请求之前取消请求将不会被执行,为了减少操作。在请求之后取消将不会发送finish消息 ,避免执行我们自定义 的finish方法
在用AsyncHttpRequest声明好runnable对象之后 提交到线程池里
- <font color="#362e2b"><font face="Arial"><font size="2">threadPool.submit(request); </font></font></font>
复制代码 这个线程池应该在AsyncHttpClient的构造函数里进行了初值的设置
- <font color="#362e2b"><font face="Arial"><font size="2">/**
- * Creates a new AsyncHttpClient with default constructor arguments values
- */
- public AsyncHttpClient() {
- this(false, 80, 443);
- } </font></font></font>
复制代码
这是他的无参构造函数,也就是我们最常用的那个。、
将调 用第二个构造 函数
- <font color="#362e2b"><font face="Arial"><font size="2">public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
- this(getDefaultSchemeRegistry(fixNoHttpResponseException, httpPort, httpsPort));
- } </font></font></font>
复制代码 getDefaultSchemeRegistry方法用于获得默认的协议模式。比如“http”或“https”同时包含一些协议属性,比如默认端口,用来为给定协议创建java.net.Socket实例的套接字工厂。SchemeRegistry类用来维持一组Scheme,当去通过请求URI建立连接时,HttpClient可以从中选择:
- <font color="#362e2b"><font face="Arial"><font size="2">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;
- } </font></font></font>
复制代码 第一个布尔型参数设置了是否验证其ssl证书。ssl是一种加密协议,
第二个参数 是http协议的端口
第三个参数 是https使用的端口
然后又将调用Asynchttpclient的第三个构造参数
- <font color="#362e2b"><font face="Arial"><font size="2">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));
- } </font></font></font>
复制代码 在设置了一些基本项之后 我们看到了线程池
- <font color="#362e2b"><font face="Arial">threadPool = Executors.newCachedThreadPool(); </font></font>
复制代码 这个线程池我在之前介绍过 这里再说一遍
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
至此get方法的执行过程就到这儿。好复杂的已经,有的函数没有分析到是因为是封装在jar包内。
其他资源分享:
【强荐!】2016年最新Android学习路线图——按此路线图学习月薪过万{ 火速收入}
Android中Bitmap,byte[],Drawable相互转化详解(超实用)
【源码分享】Android启动相机拍照并返回图片
Andriod案例源码分享--Android_系统UI布局以及设计规则
Android机器人带你玩转实现游戏2048
作者: baby14 时间: 2019-6-26 08:06
多谢分享
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |