教你寫Android網路架構之Http請求的分發與執行

來源:互聯網
上載者:User

標籤: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請求的分發與執行

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.