教你寫Android網絡框架

原文鏈接:http://blog.csdn.net/bboyfeiyu/article/details/43022703

前言

《教你寫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,由於比較簡單我們直接上代碼吧。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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

發佈了30 篇原創文章 · 獲贊 27 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章