教你寫Android網絡框架之Http請求的分發與執行
前言
在前兩篇( 教你寫Android網絡框架之基本架構 、 教你寫Android網絡框架之Request、Response類與請求隊列 )博客中,我們已經介紹了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(); } }
在啟動請求隊列時,我們會啟動指定數量的NetworkExecutor ( 參考 教你寫Android網絡框架之Request、Response類與請求隊列)。在構造NetworkExecutor時會將請求隊列以及 HttpStack注入進來,這樣NetworkExecutor就具有了兩大元素,即請求隊列和HttpStack。然后在run函數的循環中不斷地取 出請求,并且交給HttpStack執行,其間還會判斷該請求是否需要緩存、是否已經有緩存,如果使用緩存、并且已經含有緩存,那么則使用緩存的結果等。 在run函數中執行http請求,這樣就將網絡請求執行在子線程中。執行Http需要HttpStack,但最終我們需要將結果分發到UI線程需要 ResponseDelivery,下面我們挨個介紹。
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(); } 在購置請求隊列時會傳遞HttpStack,如果httpStack為空,則由HttpStackFactory根據api版本生成對應的HttpStack。即api 9以下是HttpClientStack, api 9 及其以上則為HttpUrlConnStack。 [java] view plaincopy在CODE上查看代碼片派生到我的代碼片 /** * 根據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("", "### 請求分發器退出"); } }
不管是從緩存中獲取還是從網絡上獲取,我們得到的都是一個Response對象,最后我們通過ResponseDelivery對象將結果分發給UI線程。
ResponseDelivery其實就是封裝了關聯了UI線程消息隊列的Handler,在deliveryResponse函數中將request的 deliveryResponse執行在UI線程中。既然我們有了關聯了UI線程的Handler對象,那么直接構建一個Runnable,在該 Runnable中執行request的deliveryResponse函數即可。在Request類的deliveryResponse中,又會調用 parseResponse解析Response結果,返回的結果類型就是Request
中的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就是返回的數據格式類型。比如返回的數據格式為json,那對應的請求就是JsonRequest,泛型T為JSONObject,在 JsonRequest中覆寫parseResponse函數,將得到的Response中的原始數據轉換成JSONObject。然后將請求放到隊列 中,NetworkExecutor將請求分發給HttpStack執行,執行完成之后得到Response對象,最終ResponseDelivery 將結果通過請求回調投遞到UI線程。