標籤:android multipartentity http 圖片上傳 網路架構
前言
在《教你寫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(); }}在啟動請求隊列時,我們會啟動指定數量的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。
/** * 根據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,這個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線程。
Github連結
https://github.com/bboyfeiyu/simple_net_framework
教你寫Android網路架構之Http請求的分發與執行