教你写Android网络框架之Http请求的分发与执行

来源:互联网 发布:三层更新软件 编辑:程序博客网 时间:2024/05/21 07:47

在《教你写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,由于比较简单我们直接上代码吧。

  1. /** 
  2.  * 网络请求Executor,继承自Thread,从网络请求队列中循环读取请求并且执行 
  3.  *  
  4.  * @author mrsimple 
  5.  */  
  6. final class NetworkExecutor extends Thread {  
  7.   
  8.     /** 
  9.      * 网络请求队列 
  10.      */  
  11.     private BlockingQueue<Request<?>> mRequestQueue;  
  12.     /** 
  13.      * 网络请求栈 
  14.      */  
  15.     private HttpStack mHttpStack;  
  16.     /** 
  17.      * 结果分发器,将结果投递到主线程 
  18.      */  
  19.     private static ResponseDelivery mResponseDelivery = new ResponseDelivery();  
  20.     /** 
  21.      * 请求缓存 
  22.      */  
  23.     private static Cache<String, Response> mReqCache = new LruMemCache();  
  24.     /** 
  25.      * 是否停止 
  26.      */  
  27.     private boolean isStop = false;  
  28.   
  29.     public NetworkExecutor(BlockingQueue<Request<?>> queue, HttpStack httpStack) {  
  30.         mRequestQueue = queue;  
  31.         mHttpStack = httpStack;  
  32.     }  
  33.   
  34.     @Override  
  35.     public void run() {  
  36.         try {  
  37.             while (!isStop) {  
  38.                 final Request<?> request = mRequestQueue.take();  
  39.                 if (request.isCanceled()) {  
  40.                     Log.d("### ""### 取消执行了");  
  41.                     continue;  
  42.                 }  
  43.                 Response response = null;  
  44.                 if (isUseCache(request)) {  
  45.                     // 从缓存中取  
  46.                     response = mReqCache.get(request.getUrl());  
  47.                 } else {  
  48.                     // 从网络上获取数据  
  49.                     response = mHttpStack.performRequest(request);  
  50.                     // 如果该请求需要缓存,那么请求成功则缓存到mResponseCache中  
  51.                     if (request.shouldCache() && isSuccess(response)) {  
  52.                         mReqCache.put(request.getUrl(), response);  
  53.                     }  
  54.                 }  
  55.   
  56.                 // 分发请求结果  
  57.                 mResponseDelivery.deliveryResponse(request, response);  
  58.             }  
  59.         } catch (InterruptedException e) {  
  60.             Log.i("""### 请求分发器退出");  
  61.         }  
  62.   
  63.     }  
  64.   
  65.     private boolean isSuccess(Response response) {  
  66.         return response != null && response.getStatusCode() == 200;  
  67.     }  
  68.   
  69.     private boolean isUseCache(Request<?> request) {  
  70.         return request.shouldCache() && mReqCache.get(request.getUrl()) != null;  
  71.     }  
  72.   
  73.     public void quit() {  
  74.         isStop = true;  
  75.         interrupt();  
  76.     }  
  77. }  

在启动请求队列时,我们会启动指定数量的NetworkExecutor ( 参考 教你写Android网络框架之Request、Response类与请求队列)。在构造NetworkExecutor时会将请求队列以及HttpStack注入进来,这样NetworkExecutor就具有了两大元素,即请求队列和HttpStack。然后在run函数的循环中不断地取出请求,并且交给HttpStack执行,其间还会判断该请求是否需要缓存、是否已经有缓存,如果使用缓存、并且已经含有缓存,那么则使用缓存的结果等。在run函数中执行http请求,这样就将网络请求执行在子线程中。执行Http需要HttpStack,但最终我们需要将结果分发到UI线程需要ResponseDelivery,下面我们挨个介绍。


HttpStack

HttpStack只是一个接口,只有一个performRequest函数,也就是执行请求。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 执行网络请求的接口 
  3.  *  
  4.  * @author mrsimple 
  5.  */  
  6. public interface HttpStack {  
  7.     /** 
  8.      * 执行Http请求 
  9.      *  
  10.      * @param request 待执行的请求 
  11.      * @return 
  12.      */  
  13.     public Response performRequest(Request<?> request);  
  14. }  

HttpStack是网络请求的真正执行者,有HttpClientStack和HttpUrlConnStack,两者分别为Apache的HttpClient和java的HttpURLConnection,关于这两者的区别请参考:Android访问网络,使用HttpURLConnection还是HttpClient? 默认情况下,我们会根据api版本来构建对应的HttpStack,当然用户也可以自己实现一个HttpStack,然后通过SimpleNet的工厂函数传递进来。

例如 : 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * @param coreNums 线程核心数 
  3.  * @param httpStack http执行器 
  4.  */  
  5. protected RequestQueue(int coreNums, HttpStack httpStack) {  
  6.     mDispatcherNums = coreNums;  
  7.     mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();  
  8. }  

在购置请求队列时会传递HttpStack,如果httpStack为空,则由HttpStackFactory根据api版本生成对应的HttpStack。即api 9以下是HttpClientStack, api 9 及其以上则为HttpUrlConnStack。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 根据api版本选择HttpClient或者HttpURLConnection 
  3.  *  
  4.  * @author mrsimple 
  5.  */  
  6. public final class HttpStackFactory {  
  7.   
  8.     private static final int GINGERBREAD_SDK_NUM = 9;  
  9.   
  10.     /** 
  11.      * 根据SDK版本号来创建不同的Http执行器,即SDK 9之前使用HttpClient,之后则使用HttlUrlConnection, 
  12.      * 两者之间的差别请参考 : 
  13.      * http://android-developers.blogspot.com/2011/09/androids-http-clients.html 
  14.      *  
  15.      * @return 
  16.      */  
  17.     public static HttpStack createHttpStack() {  
  18.         int runtimeSDKApi = Build.VERSION.SDK_INT;  
  19.         if (runtimeSDKApi >= GINGERBREAD_SDK_NUM) {  
  20.             return new HttpUrlConnStack();  
  21.         }  
  22.   
  23.         return new HttpClientStack();  
  24.     }  
  25. }  

HttpClientStack和HttpUrlConnStack分别就是封装了HttpClient和HttpURLConnection的http请求,构建请求、设置header、设置请求参数、解析Response等操作。针对于这一层,我们没有给出一个抽象类,原因是HttpClient和HttpURLConnection并不属于同一个类族,他们的行为虽然都很相似,但是其中涉及到的一些类型却是不同的。这里我们给出HttpUrlConnStack的示例,最近比较忙,因此写的配置比较简单,有需要的同学自己优化了。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 使用HttpURLConnection执行网络请求的HttpStack 
  3.  *  
  4.  * @author mrsimple 
  5.  */  
  6. public class HttpUrlConnStack implements HttpStack {  
  7.   
  8.     /** 
  9.      * 配置Https 
  10.      */  
  11.     HttpUrlConnConfig mConfig = HttpUrlConnConfig.getConfig();  
  12.   
  13.     @Override  
  14.     public Response performRequest(Request<?> request) {  
  15.         HttpURLConnection urlConnection = null;  
  16.         try {  
  17.             // 构建HttpURLConnection  
  18.             urlConnection = createUrlConnection(request.getUrl());  
  19.             // 设置headers  
  20.             setRequestHeaders(urlConnection, request);  
  21.             // 设置Body参数  
  22.             setRequestParams(urlConnection, request);  
  23.             // https 配置  
  24.             configHttps(request);  
  25.             return fetchResponse(urlConnection);  
  26.         } catch (Exception e) {  
  27.             e.printStackTrace();  
  28.         } finally {  
  29.             if (urlConnection != null) {  
  30.                 urlConnection.disconnect();  
  31.             }  
  32.         }  
  33.         return null;  
  34.     }  
  35.   
  36.     private HttpURLConnection createUrlConnection(String url) throws IOException {  
  37.         URL newURL = new URL(url);  
  38.         URLConnection urlConnection = newURL.openConnection();  
  39.         urlConnection.setConnectTimeout(mConfig.connTimeOut);  
  40.         urlConnection.setReadTimeout(mConfig.soTimeOut);  
  41.         urlConnection.setDoInput(true);  
  42.         urlConnection.setUseCaches(false);  
  43.         return (HttpURLConnection) urlConnection;  
  44.     }  
  45.   
  46.     private void configHttps(Request<?> request) {  
  47.         if (request.isHttps()) {  
  48.             SSLSocketFactory sslFactory = mConfig.getSslSocketFactory();  
  49.             // 配置https  
  50.             if (sslFactory != null) {  
  51.                 HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);  
  52.                 HttpsURLConnection.setDefaultHostnameVerifier(mConfig.getHostnameVerifier());  
  53.             }  
  54.   
  55.         }  
  56.     }  
  57.   
  58.     private void setRequestHeaders(HttpURLConnection connection, Request<?> request) {  
  59.         Set<String> headersKeys = request.getHeaders().keySet();  
  60.         for (String headerName : headersKeys) {  
  61.             connection.addRequestProperty(headerName, request.getHeaders().get(headerName));  
  62.         }  
  63.     }  
  64.   
  65.     protected void setRequestParams(HttpURLConnection connection, Request<?> request)  
  66.             throws ProtocolException, IOException {  
  67.         HttpMethod method = request.getHttpMethod();  
  68.         connection.setRequestMethod(method.toString());  
  69.         // add params  
  70.         byte[] body = request.getBody();  
  71.         if (body != null) {  
  72.             // enable output  
  73.             connection.setDoOutput(true);  
  74.             // set content type  
  75.             connection  
  76.                     .addRequestProperty(Request.HEADER_CONTENT_TYPE, request.getBodyContentType());  
  77.             // write params data to connection  
  78.             DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());  
  79.             dataOutputStream.write(body);  
  80.             dataOutputStream.close();  
  81.         }  
  82.     }  
  83.   
  84.     private Response fetchResponse(HttpURLConnection connection) throws IOException {  
  85.   
  86.         // Initialize HttpResponse with data from the HttpURLConnection.  
  87.         ProtocolVersion protocolVersion = new ProtocolVersion("HTTP"11);  
  88.         int responseCode = connection.getResponseCode();  
  89.         if (responseCode == -1) {  
  90.             throw new IOException("Could not retrieve response code from HttpUrlConnection.");  
  91.         }  
  92.         // 状态行数据  
  93.         StatusLine responseStatus = new BasicStatusLine(protocolVersion,  
  94.                 connection.getResponseCode(), connection.getResponseMessage());  
  95.         // 构建response  
  96.         Response response = new Response(responseStatus);  
  97.         // 设置response数据  
  98.         response.setEntity(entityFromURLConnwction(connection));  
  99.         addHeadersToResponse(response, connection);  
  100.         return response;  
  101.     }  
  102.   
  103.     /** 
  104.      * 执行HTTP请求之后获取到其数据流,即返回请求结果的流 
  105.      *  
  106.      * @param connection 
  107.      * @return 
  108.      */  
  109.     private HttpEntity entityFromURLConnwction(HttpURLConnection connection) {  
  110.         BasicHttpEntity entity = new BasicHttpEntity();  
  111.         InputStream inputStream = null;  
  112.         try {  
  113.             inputStream = connection.getInputStream();  
  114.         } catch (IOException e) {  
  115.             e.printStackTrace();  
  116.             inputStream = connection.getErrorStream();  
  117.         }  
  118.   
  119.         // TODO : GZIP   
  120.         entity.setContent(inputStream);  
  121.         entity.setContentLength(connection.getContentLength());  
  122.         entity.setContentEncoding(connection.getContentEncoding());  
  123.         entity.setContentType(connection.getContentType());  
  124.   
  125.         return entity;  
  126.     }  
  127.   
  128.     private void addHeadersToResponse(BasicHttpResponse response, HttpURLConnection connection) {  
  129.         for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {  
  130.             if (header.getKey() != null) {  
  131.                 Header h = new BasicHeader(header.getKey(), header.getValue().get(0));  
  132.                 response.addHeader(h);  
  133.             }  
  134.         }  
  135.     }  
  136.   
  137. }  
代码很简单,就不多说了。

ResponseDelivery

在HttpStack的performRequest函数中,我们会返回一个Response对象,该对象包含了我们请求对应的Response。关于Response类你不太了解的可以参考教你写Android网络框架之Request、Response类与请求队列。我们在NetworkExecutor中执行http请求的最后一步会将结果分发给UI线程,主要工作其实就是将请求的回调执行到UI线程,以便用户可以更新UI等操作。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void run() {  
  3.     try {  
  4.         while (!isStop) {  
  5.             final Request<?> request = mRequestQueue.take();  
  6.             if (request.isCanceled()) {  
  7.                 Log.d("### ""### 取消执行了");  
  8.                 continue;  
  9.             }  
  10.             Response response = null;  
  11.             if (isUseCache(request)) {  
  12.                 // 从缓存中取  
  13.                 response = mReqCache.get(request.getUrl());  
  14.             } else {  
  15.                 // 从网络上获取数据  
  16.                 response = mHttpStack.performRequest(request);  
  17.                 // 如果该请求需要缓存,那么请求成功则缓存到mResponseCache中  
  18.                 if (request.shouldCache() && isSuccess(response)) {  
  19.                     mReqCache.put(request.getUrl(), response);  
  20.                 }  
  21.             }  
  22.   
  23.             // 分发请求结果  
  24.             mResponseDelivery.deliveryResponse(request, response);  
  25.         }  
  26.     } catch (InterruptedException e) {  
  27.         Log.i("""### 请求分发器退出");  
  28.     }  
  29.   
  30. }  
不管是从缓存中获取还是从网络上获取,我们得到的都是一个Response对象,最后我们通过ResponseDelivery对象将结果分发给UI线程。

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代码如下 :

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 请求结果投递类,将请求结果投递给UI线程 
  3.  *  
  4.  * @author mrsimple 
  5.  */  
  6. class ResponseDelivery implements Executor {  
  7.   
  8.     /** 
  9.      * 主线程的hander 
  10.      */  
  11.     Handler mResponseHandler = new Handler(Looper.getMainLooper());  
  12.   
  13.     /** 
  14.      * 处理请求结果,将其执行在UI线程 
  15.      *  
  16.      * @param request 
  17.      * @param response 
  18.      */  
  19.     public void deliveryResponse(final Request<?> request, final Response response) {  
  20.         Runnable respRunnable = new Runnable() {  
  21.   
  22.             @Override  
  23.             public void run() {  
  24.                 request.deliveryResponse(response);  
  25.             }  
  26.         };  
  27.   
  28.         execute(respRunnable);  
  29.     }  
  30.   
  31.     @Override  
  32.     public void execute(Runnable command) {  
  33.         mResponseHandler.post(command);  
  34.     }  
  35.   
  36. }  

Request类的deliveryResponse函数。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 处理Response,该方法运行在UI线程. 
  3.  *  
  4.  * @param response 
  5.  */  
  6. public final void deliveryResponse(Response response) {  
  7.     T result = parseResponse(response);  
  8.     if (mRequestListener != null) {  
  9.         int stCode = response != null ? response.getStatusCode() : -1;  
  10.         String msg = response != null ? response.getMessage() : "unkown error";  
  11.         mRequestListener.onComplete(stCode, result, msg);  
  12.     }  
  13. }  

这样,整个请求过程就完成了。下面我们总结一下这个过程。

不同用户的服务器返回的数据格式是不一致的,因此我们定义了Request<T>泛型基类,泛型T就是返回的数据格式类型。比如返回的数据格式为json,那对应的请求就是JsonRequest,泛型T为JSONObject,在JsonRequest中覆写parseResponse函数,将得到的Response中的原始数据转换成JSONObject。然后将请求放到队列中,NetworkExecutor将请求分发给HttpStack执行,执行完成之后得到Response对象,最终ResponseDelivery将结果通过请求回调投递到UI线程。

Github链接

https://github.com/bboyfeiyu/simple_net_framework


0 0
原创粉丝点击