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

jopen 9年前發布 | 30K 次閱讀 Android Android開發 移動開發

 

前言

在前兩篇( 教你寫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.

教你寫Android網絡框架之Http請求的分發與執行 圖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線程。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!