前言
在《教你写Android网络框架》专栏的前两篇博客中,我们已经介绍了SimpleNet框架的基本结构,以及Request、Response、请求队列的实现,以及为什么要这么设计,这么设计的考虑是什么。前两篇博客中已经介绍了各个角色,今天我们就来剖析另外几个特别重要的角色,即NetworkExecutor、HttpStack以及ResponseDelivery,它们分别对应的功能是网络请求线程、Http执行器、Response分发,这三者是执行http请求和处理Response的核心。
我们再来回顾一下,SimpleNet各个角色的分工合作。首先用户需要创建一个请求队列,然后将各个请求添加到请求队列中。多个NetworkExecutor ( 实质上是一个线程 )共享一个消息队列,在各个NetworkExecutor中循环的取请求队列中的请求,拿到一个请求,然后通过HttpStack来执行Http请求,请求完成后最终通过ResponseDelivery将Response结果分发到UI线程,保证请求回调执行在UI线程,这样用户就可以直接在回调中更新UI。执行流程如图1.
图1
还有不太了解这幅架构图的可以参考专栏中的第一篇博客。
NetworkExecutor
作为SimpleNet中的“心脏”,NetworkExecutor起着非常重要的作用。之所以称之为“心脏”,是由于NetworkExecutor的功能是源源不断地从请求队列中获取请求,然后交给HttpStack来执行。它就像汽车中的发动机,人体中的心脏一样,带动着整个框架的运行。
NetworkExecutor实质上是一个Thread,在run方法中我们会执行一个循环,不断地从请求队列中取得请求,然后交给HttpStack,由于比较简单我们直接上代码吧。
- /**
- * 网络请求Executor,继承自Thread,从网络请求队列中循环读取请求并且执行
- *
- * @author mrsimple
- */
- final class NetworkExecutor extends Thread {
- /**
- * 网络请求队列
- */
- private BlockingQueue<Request<?>> mRequestQueue;
- /**
- * 网络请求栈
- */
- private HttpStack mHttpStack;
- /**
- * 结果分发器,将结果投递到主线程
- */
- private static ResponseDelivery mResponseDelivery = new ResponseDelivery();
- /**
- * 请求缓存
- */
- private static Cache<String, Response> mReqCache = new LruMemCache();
- /**
- * 是否停止
- */
- private boolean isStop = false;
- public NetworkExecutor(BlockingQueue<Request<?>> queue, HttpStack httpStack) {
- mRequestQueue = queue;
- mHttpStack = httpStack;
- }
- @Override
- public void run() {
- try {
- while (!isStop) {
- final Request<?> request = mRequestQueue.take();
- if (request.isCanceled()) {
- Log.d("### ", "### 取消执行了");
- continue;
- }
- Response response = null;
- if (isUseCache(request)) {
- // 从缓存中取
- response = mReqCache.get(request.getUrl());
- } else {
- // 从网络上获取数据
- response = mHttpStack.performRequest(request);
- // 如果该请求需要缓存,那么请求成功则缓存到mResponseCache中
- if (request.shouldCache() && isSuccess(response)) {
- mReqCache.put(request.getUrl(), response);
- }
- }
- // 分发请求结果
- mResponseDelivery.deliveryResponse(request, response);
- }
- } catch (InterruptedException e) {
- Log.i("", "### 请求分发器退出");
- }
- }
- private boolean isSuccess(Response response) {
- return response != null && response.getStatusCode() == 200;
- }
- private boolean isUseCache(Request<?> request) {
- return request.shouldCache() && mReqCache.get(request.getUrl()) != null;
- }
- public void quit() {
- isStop = true;
- interrupt();
- }
- }
HttpStack
HttpStack只是一个接口,只有一个performRequest函数,也就是执行请求。
- /**
- * 执行网络请求的接口
- *
- * @author mrsimple
- */
- public interface HttpStack {
- /**
- * 执行Http请求
- *
- * @param request 待执行的请求
- * @return
- */
- public Response performRequest(Request<?> request);
- }
HttpStack是网络请求的真正执行者,有HttpClientStack和HttpUrlConnStack,两者分别为Apache的HttpClient和java的HttpURLConnection,关于这两者的区别请参考:Android访问网络,使用HttpURLConnection还是HttpClient? 默认情况下,我们会根据api版本来构建对应的HttpStack,当然用户也可以自己实现一个HttpStack,然后通过SimpleNet的工厂函数传递进来。
例如 :
- /**
- * @param coreNums 线程核心数
- * @param httpStack http执行器
- */
- protected RequestQueue(int coreNums, HttpStack httpStack) {
- mDispatcherNums = coreNums;
- mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();
- }
- /**
- * 根据api版本选择HttpClient或者HttpURLConnection
- *
- * @author mrsimple
- */
- public final class HttpStackFactory {
- private static final int GINGERBREAD_SDK_NUM = 9;
- /**
- * 根据SDK版本号来创建不同的Http执行器,即SDK 9之前使用HttpClient,之后则使用HttlUrlConnection,
- * 两者之间的差别请参考 :
- * http://android-developers.blogspot.com/2011/09/androids-http-clients.html
- *
- * @return
- */
- public static HttpStack createHttpStack() {
- int runtimeSDKApi = Build.VERSION.SDK_INT;
- if (runtimeSDKApi >= GINGERBREAD_SDK_NUM) {
- return new HttpUrlConnStack();
- }
- return new HttpClientStack();
- }
- }
HttpClientStack和HttpUrlConnStack分别就是封装了HttpClient和HttpURLConnection的http请求,构建请求、设置header、设置请求参数、解析Response等操作。针对于这一层,我们没有给出一个抽象类,原因是HttpClient和HttpURLConnection并不属于同一个类族,他们的行为虽然都很相似,但是其中涉及到的一些类型却是不同的。这里我们给出HttpUrlConnStack的示例,最近比较忙,因此写的配置比较简单,有需要的同学自己优化了。
- /**
- * 使用HttpURLConnection执行网络请求的HttpStack
- *
- * @author mrsimple
- */
- public class HttpUrlConnStack implements HttpStack {
- /**
- * 配置Https
- */
- HttpUrlConnConfig mConfig = HttpUrlConnConfig.getConfig();
- @Override
- public Response performRequest(Request<?> request) {
- HttpURLConnection urlConnection = null;
- try {
- // 构建HttpURLConnection
- urlConnection = createUrlConnection(request.getUrl());
- // 设置headers
- setRequestHeaders(urlConnection, request);
- // 设置Body参数
- setRequestParams(urlConnection, request);
- // https 配置
- configHttps(request);
- return fetchResponse(urlConnection);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
- return null;
- }
- private HttpURLConnection createUrlConnection(String url) throws IOException {
- URL newURL = new URL(url);
- URLConnection urlConnection = newURL.openConnection();
- urlConnection.setConnectTimeout(mConfig.connTimeOut);
- urlConnection.setReadTimeout(mConfig.soTimeOut);
- urlConnection.setDoInput(true);
- urlConnection.setUseCaches(false);
- return (HttpURLConnection) urlConnection;
- }
- private void configHttps(Request<?> request) {
- if (request.isHttps()) {
- SSLSocketFactory sslFactory = mConfig.getSslSocketFactory();
- // 配置https
- if (sslFactory != null) {
- HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
- HttpsURLConnection.setDefaultHostnameVerifier(mConfig.getHostnameVerifier());
- }
- }
- }
- private void setRequestHeaders(HttpURLConnection connection, Request<?> request) {
- Set<String> headersKeys = request.getHeaders().keySet();
- for (String headerName : headersKeys) {
- connection.addRequestProperty(headerName, request.getHeaders().get(headerName));
- }
- }
- protected void setRequestParams(HttpURLConnection connection, Request<?> request)
- throws ProtocolException, IOException {
- HttpMethod method = request.getHttpMethod();
- connection.setRequestMethod(method.toString());
- // add params
- byte[] body = request.getBody();
- if (body != null) {
- // enable output
- connection.setDoOutput(true);
- // set content type
- connection
- .addRequestProperty(Request.HEADER_CONTENT_TYPE, request.getBodyContentType());
- // write params data to connection
- DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
- dataOutputStream.write(body);
- dataOutputStream.close();
- }
- }
- private Response fetchResponse(HttpURLConnection connection) throws IOException {
- // Initialize HttpResponse with data from the HttpURLConnection.
- ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
- int responseCode = connection.getResponseCode();
- if (responseCode == -1) {
- throw new IOException("Could not retrieve response code from HttpUrlConnection.");
- }
- // 状态行数据
- StatusLine responseStatus = new BasicStatusLine(protocolVersion,
- connection.getResponseCode(), connection.getResponseMessage());
- // 构建response
- Response response = new Response(responseStatus);
- // 设置response数据
- response.setEntity(entityFromURLConnwction(connection));
- addHeadersToResponse(response, connection);
- return response;
- }
- /**
- * 执行HTTP请求之后获取到其数据流,即返回请求结果的流
- *
- * @param connection
- * @return
- */
- private HttpEntity entityFromURLConnwction(HttpURLConnection connection) {
- BasicHttpEntity entity = new BasicHttpEntity();
- InputStream inputStream = null;
- try {
- inputStream = connection.getInputStream();
- } catch (IOException e) {
- e.printStackTrace();
- inputStream = connection.getErrorStream();
- }
- // TODO : GZIP
- entity.setContent(inputStream);
- entity.setContentLength(connection.getContentLength());
- entity.setContentEncoding(connection.getContentEncoding());
- entity.setContentType(connection.getContentType());
- return entity;
- }
- private void addHeadersToResponse(BasicHttpResponse response, HttpURLConnection connection) {
- for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
- if (header.getKey() != null) {
- Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
- response.addHeader(h);
- }
- }
- }
- }
ResponseDelivery
在HttpStack的performRequest函数中,我们会返回一个Response对象,该对象包含了我们请求对应的Response。关于Response类你不太了解的可以参考教你写Android网络框架之Request、Response类与请求队列。我们在NetworkExecutor中执行http请求的最后一步会将结果分发给UI线程,主要工作其实就是将请求的回调执行到UI线程,以便用户可以更新UI等操作。
- @Override
- public void run() {
- try {
- while (!isStop) {
- final Request<?> request = mRequestQueue.take();
- if (request.isCanceled()) {
- Log.d("### ", "### 取消执行了");
- continue;
- }
- Response response = null;
- if (isUseCache(request)) {
- // 从缓存中取
- response = mReqCache.get(request.getUrl());
- } else {
- // 从网络上获取数据
- response = mHttpStack.performRequest(request);
- // 如果该请求需要缓存,那么请求成功则缓存到mResponseCache中
- if (request.shouldCache() && isSuccess(response)) {
- mReqCache.put(request.getUrl(), response);
- }
- }
- // 分发请求结果
- mResponseDelivery.deliveryResponse(request, response);
- }
- } catch (InterruptedException e) {
- Log.i("", "### 请求分发器退出");
- }
- }
ResponseDelivery其实就是封装了关联了UI线程消息队列的Handler,在deliveryResponse函数中将request的deliveryResponse执行在UI线程中。既然我们有了关联了UI线程的Handler对象,那么直接构建一个Runnable,在该Runnable中执行request的deliveryResponse函数即可。在Request类的deliveryResponse中,又会调用parseResponse解析Response结果,返回的结果类型就是Request<T>中的T,这个T是在Request子类中指定,例如JsonRequest,那么返回的Response的结果就是JSONObject。这样我们就得到了服务器返回的json数据,并且将这个json结果通过回调的形式传递给了UI线程。用户就可以在该回调中更新UI了。
这其中主要就是抽象和泛型,写框架很多时候泛型是很重要的手段,因此熟悉使用抽象和泛型是面向对象开发的重要一步。
ResponseDelivery代码如下 :
- /**
- * 请求结果投递类,将请求结果投递给UI线程
- *
- * @author mrsimple
- */
- class ResponseDelivery implements Executor {
- /**
- * 主线程的hander
- */
- Handler mResponseHandler = new Handler(Looper.getMainLooper());
- /**
- * 处理请求结果,将其执行在UI线程
- *
- * @param request
- * @param response
- */
- public void deliveryResponse(final Request<?> request, final Response response) {
- Runnable respRunnable = new Runnable() {
- @Override
- public void run() {
- request.deliveryResponse(response);
- }
- };
- execute(respRunnable);
- }
- @Override
- public void execute(Runnable command) {
- mResponseHandler.post(command);
- }
- }
Request类的deliveryResponse函数。
- /**
- * 处理Response,该方法运行在UI线程.
- *
- * @param response
- */
- public final void deliveryResponse(Response response) {
- T result = parseResponse(response);
- if (mRequestListener != null) {
- int stCode = response != null ? response.getStatusCode() : -1;
- String msg = response != null ? response.getMessage() : "unkown error";
- mRequestListener.onComplete(stCode, result, msg);
- }
- }
这样,整个请求过程就完成了。下面我们总结一下这个过程。
不同用户的服务器返回的数据格式是不一致的,因此我们定义了Request<T>泛型基类,泛型T就是返回的数据格式类型。比如返回的数据格式为json,那对应的请求就是JsonRequest,泛型T为JSONObject,在JsonRequest中覆写parseResponse函数,将得到的Response中的原始数据转换成JSONObject。然后将请求放到队列中,NetworkExecutor将请求分发给HttpStack执行,执行完成之后得到Response对象,最终ResponseDelivery将结果通过请求回调投递到UI线程。