android-async-http-master 源码解读

第一次写源码解读这种文章,不专业还请多指教哈、

先介绍一下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包内的,看不了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值