Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 網絡請求框架

網絡請求框架

編輯:關於Android編程

前言

我們已經介紹了SimpleNet網絡框架的基本結構,今天我們就開始從代碼的角度來開始切入該網絡框架的實現,在剖析的同時我們會分析設計思路,以及為什麼要這樣做,這樣做的好處是什麼。這樣我們不僅學到了如何實現網絡框架,也會學到設計一個通用的框架應該有哪些考慮,這就擴展到框架設計的范疇,通過這個簡單的實例希望能給新人一些幫助。當然這只是一家之言,大家都可以有自己的實現思路。

正如你所看到的,這系列博客是為新人准備的,如果你是高手,請忽略。

在框架開發當中,很重要的一點就是抽象。也就是面向對象中重要的一條原則: 依賴倒置原則,簡單來說就是要依賴抽象,而不依賴具體。這樣就使得我們的框架具有可擴展性,同時也滿足了開閉原則,即對擴展開放,對修改關閉。針對於我們的網絡框架來說,最重要的抽象就是Reqeust類、Response類,因此今天我們就從兩個類開始切入。最後我們再引入網絡框架中的請求隊列(RequestQueue),這是SimpleNet中的中樞神經,所有的請求都需要放到該隊列,然後等待著被執行。請求隊列就像工廠中的流水線一樣,而網絡請求就像流水線上的待加工的產品。執行網絡請求的對象就類似工廠中的工人,在自己的崗位上等待流水線上傳遞過來的產品,然後對其加工,加工完就將產品放到其他的位置。它們角色對應關系參考圖1

圖1

Request類

既然網絡框架,那麼我們先從網絡請求類開始。前文已經說過,既然是框架,那麼就需要可擴展性。因此注定了Request是抽象,而不是具體。而對於網絡請求來說,用戶得到的請求結果格式是不確定,比如有的服務器返回的是json,有的返回的是xml,有的直接是字符串。但是對於Http Response來說,它的返回數據類型都是Stream,也就是我們得到的原始數據都是二進制的流。所以在Request基類中我們必須預留方法來解析Response返回的具體類型,雖然返回的類型不同,但是他們的處理邏輯是一樣的,因此我們可把Request作為泛型類,它的泛型類型就是它的返回數據類型,比如Request,那麼它的返回數據類型就是String類型的。另外還有請求的優先級、可取消等,我們這裡先給出核心代碼,然後再繼續分析。

/** 
 * 網絡請求類. 注意GET和DELETE不能傳遞請求參數,因為其請求的性質所致,用戶可以將參數構建到url後傳遞進來到Request中. 
 *  
 * @author mrsimple 
 * @param  T為請求返回的數據類型 
 */  
public abstract class Request implements Comparable> {  

    /** 
     * http請求方法枚舉,這裡我們只有GET, POST, PUT, DELETE四種 
     * @author mrsimple 
     */  
    public static enum HttpMethod {  
        GET("GET"),  
        POST("POST"),  
        PUT("PUT"),  
        DELETE("DELETE");  

        /** http request type */  
        private String mHttpMethod = "";  

        private HttpMethod(String method) {  
            mHttpMethod = method;  
        }  

        @Override  
        public String toString() {  
            return mHttpMethod;  
        }  
    }  

    /** 
     * 優先級枚舉 
     * @author mrsimple 
     */  
    public static enum Priority {  
        LOW,  
        NORMAL,  
        HIGN,  
        IMMEDIATE  
    }  

    /** 
     * Default encoding for POST or PUT parameters. See 
     * {@link #getParamsEncoding()}. 
     */  
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";  
    /** 
     * 請求序列號 
     */  
    protected int mSerialNum = 0;  
    /** 
     * 優先級默認設置為Normal 
     */  
    protected Priority mPriority = Priority.NORMAL;  
    /** 
     * 是否取消該請求 
     */  
    protected boolean isCancel = false;  

    /** 該請求是否應該緩存 */  
    private boolean mShouldCache = true;  
    /** 
     * 請求Listener 
     */  
    protected RequestListener mRequestListener;  
    /** 
     * 請求的url 
     */  
    private String mUrl = "";  
    /** 
     * 請求的方法 
     */  
    HttpMethod mHttpMethod = HttpMethod.GET;  

    /** 
     * 請求的header 
     */  
    private Map mHeaders = new HashMap();  
    /** 
     * 請求參數 
     */  
    private Map mBodyParams = new HashMap();  

    public Request(HttpMethod method, String url, RequestListener listener) {  
        mHttpMethod = method;  
        mUrl = url;  
        mRequestListener = listener;  
    }  

    /** 
     * 從原生的網絡請求中解析結果,子類覆寫 
     *  
     * @param response 
     * @return 
     */  
    public abstract T parseResponse(Response response);  

    /** 
     * 處理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);  
        }  
    }  

    public int getSerialNumber() {  
        return mSerialNum;  
    }  

    protected String getParamsEncoding() {  
        return DEFAULT_PARAMS_ENCODING;  
    }  

    public String getBodyContentType() {  
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();  
    }  

    public HttpMethod getHttpMethod() {  
        return mHttpMethod;  
    }  

    public Map getHeaders() {  
        return mHeaders;  
    }  

    public Map getParams() {  
        return mBodyParams;  
    }  

    /** 
     * 返回POST或者PUT請求時的Body參數字節數組 
     */  
    public byte[] getBody() {  
        Map params = getParams();  
        if (params != null && params.size() > 0) {  
            return encodeParameters(params, getParamsEncoding());  
        }  
        return null;  
    }  

    /** 
     * 將參數轉換為Url編碼的參數串 
     */  
    private byte[] encodeParameters(Map params, String paramsEncoding) {  
        StringBuilder encodedParams = new StringBuilder();  
        try {  
            for (Map.Entry entry : params.entrySet()) {  
                encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));  
                encodedParams.append('=');  
                encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));  
                encodedParams.append('&');  
            }  
            return encodedParams.toString().getBytes(paramsEncoding);  
        } catch (UnsupportedEncodingException uee) {  
            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);  
        }  
    }  

    // 用於對請求的排序處理,根據優先級和加入到隊列的序號進行排序  
    @Override  
    public int compareTo(Request another) {  
        Priority myPriority = this.getPriority();  
        Priority anotherPriority = another.getPriority();  
        // 如果優先級相等,那麼按照添加到隊列的序列號順序來執行  
        return myPriority.equals(another) ? this.getSerialNumber() - another.getSerialNumber()  
                : myPriority.ordinal() - anotherPriority.ordinal();  
    }  

    /** 
     * 網絡請求Listener,會被執行在UI線程 
     * @author mrsimple 
     * @param  請求的response類型 
     */  
    public static interface RequestListener {  
        /** 
         * 請求完成的回調 
         * @param response 
         */  
        public void onComplete(int stCode, T response, String errMsg);  
    }  
}

上述代碼Request為抽象類,T則為該請求Response的數據格式。這個T是請求類中的一個比較重要的點,不同的人有不同的需求,即請求Reponse的數據格式並不是都是一樣的,我們必須考慮到請求返回類型的多樣性,用泛型T來表示返回的數據格式類型,然後Request子類覆寫對應的方法實現解析Response的數據格式,最後調用請求Listener將請求結果執行在UI線程,這樣整個請求就完成了。

每個Request都有一個序列號,該序列號由請求隊列生成,標識該請求在隊列中的序號,該序號和請求優先級決定了該請求在隊列中的排序,即它在請求隊列的執行順序。每個請求有請求方式,例如”POST”、”GET”,這裡我們用枚舉來代替,具名類型比單純的字符串更易於使用。每個Request都可以添加Header、Body參數 ( 關於請求參數的格式可以參考 四種常見的 POST 提交數據方式),並且可以取消。抽象類封裝了通用的代碼,只有可變的部分是抽象函數,這裡只有parseResponse這個函數。例如,我們返回的數據格式是Json,那麼我們構建一個子類叫做JsonRequest,示例代碼如下。

/** 
 * 返回的數據類型為Json的請求, Json對應的對象類型為JSONObject 
 *  
 * @author mrsimple 
 */  
public class JsonRequest extends Request {  

    public JsonRequest(HttpMethod method, String url, RequestListener listener) {  
        super(method, url, listener);  
    }  


    /** 
     * 將Response的結果轉換為JSONObject 
     */  
    @Override  
    public JSONObject parseResponse(Response response) {  
        String jsonString = new String(response.getRawData());  
        try {  
            return new JSONObject(jsonString);  
        } catch (JSONException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
}

可以看到,實現一個請求類還是非常簡單的,只需要覆寫parseResponse函數來解析你的請求返回的數據即可。這樣就保證了可擴展性,比如後面如果我想使用這個框架來做一個ImageLoader,那麼我可以創建一個ImageRequest,該請求返回的類型就是Bitmap,那麼我們只需要覆寫parseResponse函數,然後把結果轉換成Bitmap即可。

這裡引入了Response類,這個Response類存儲了請求的狀態碼、請求結果等內容,我們繼續往下看。

Response類

每個請求都對應一個Response,但這裡的問題是這個Response的數據格式我們是不知道的。我們寫的是框架,不是應用。框架只是構建一個基本環境,並且附帶一些比較常用的類,比如這裡的JsonRequest。但是重要的一點是可以讓用戶自由、簡單的擴展以實現他的需求。對於Response類來說,我們最重要的一點就是要確定請求結果的數據格式類型。我們都知道,HTTP實際上是基於TCP協議,而TCP協議又是基於Socket,Socket實際上操作的也就是輸入、輸出流,輸出流是向服務器寫數據,輸入流自然是從服務器讀取數據。因此我們在Response類中應該使用InputStream存儲結果或者使用更為易於使用的字節數組,這裡我們使用字節數組來存儲。我們來看Response類。

/** 
 * 請求結果類,繼承自BasicHttpResponse,將結果存儲在rawData中. 
 * @author mrsimple 
 */  
public class Response extends BasicHttpResponse {  

    public byte[] rawData = new byte[0];  

    public Response(StatusLine statusLine) {  
        super(statusLine);  
    }  

    public Response(ProtocolVersion ver, int code, String reason) {  
        super(ver, code, reason);  
    }  

    @Override  
    public void setEntity(HttpEntity entity) {  
        super.setEntity(entity);  
        rawData = entityToBytes(getEntity());  
    }  

    public byte[] getRawData() {  
        return rawData;  
    }  

    public int getStatusCode() {  
        return getStatusLine().getStatusCode();  
    }  

    public String getMessage() {  
        return getStatusLine().getReasonPhrase();  
    }  

    /** Reads the contents of HttpEntity into a byte[]. */  
    private byte[] entityToBytes(HttpEntity entity) {  
        try {  
            return EntityUtils.toByteArray(entity);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return new byte[0];  
    }  

}

這個類很簡單,只是繼承了BasicHttpResponse,然後將輸入流轉換成字節數組,然後包裝了幾個常用的方法,主要是為了使用簡單吧。我們將結果存儲為字節數組,這樣可以用戶可以很方便的將結果轉換為String、bitmap等數據類型,如果直接存儲的是InputStream,那麼在很多時候用戶需要在外圍將InputStream先轉換為字節數組,然後再轉換為最終的格式,例如InputStream轉為String類型。這也是為什麼我們這裡選用byte[]而不用InputStream的原因。

請求隊列

網絡請求隊列也比較簡單,實際上就是內部封裝了一個優先級隊列,在構建隊列時會啟動幾個NetworkExecutor ( 子線程 )來從請求隊列中獲取請求,並且執行請求。請求隊列會根據請求的優先級進行排序,這樣就保證了一些優先級高的請求得到盡快的處理,這也就是為什麼Request類中實現了Comparable接口的原因。如果優先級一致的情況下,則會根據請求加入到隊列的順序來排序,這個序號由請求隊列生成,這樣就保證了優先級一樣的情況下按照FIFO的策略執行。
/** 
 * 請求隊列, 使用優先隊列,使得請求可以按照優先級進行處理.
 *  
 * @author mrsimple 
 */  
public final class RequestQueue {  
    /** 
     * 請求隊列 [ Thread-safe ] 
     */  
    private BlockingQueue> mRequestQueue = new PriorityBlockingQueue>();  
    /** 
     * 請求的序列化生成器 
     */  
    private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);  

    /** 
     * 默認的核心數 
     */  
    public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors() + 1;  
    /** 
     * CPU核心數 + 1個分發線程數 
     */  
    private int mDispatcherNums = DEFAULT_CORE_NUMS;  
    /** 
     * NetworkExecutor,執行網絡請求的線程 
     */  
    private NetworkExecutor[] mDispatchers = null;  
    /** 
     * Http請求的真正執行者 
     */  
    private HttpStack mHttpStack;  

    /** 
     * @param coreNums 線程核心數 
     */  
    protected RequestQueue(int coreNums, HttpStack httpStack) {  
        mDispatcherNums = coreNums;  
        mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();  
    }  

    /** 
     * 啟動NetworkExecutor 
     */  
    private final void startNetworkExecutors() {  
        mDispatchers = new NetworkExecutor[mDispatcherNums];  
        for (int i = 0; i < mDispatcherNums; i++) {  
            mDispatchers[i] = new NetworkExecutor(mRequestQueue, mHttpStack);  
            mDispatchers[i].start();  
        }  
    }  

    public void start() {  
        stop();  
        startNetworkExecutors();  
    }  

    /** 
     * 停止NetworkExecutor 
     */  
    public void stop() {  
        if (mDispatchers != null && mDispatchers.length > 0) {  
            for (int i = 0; i < mDispatchers.length; i++) {  
                mDispatchers[i].quit();  
            }  
        }  
    }  

    /** 
     * 不能重復添加請求 
     *  
     * @param request 
     */  
    public void addRequest(Request request) {  
        if (!mRequestQueue.contains(request)) {  
            request.setSerialNumber(this.generateSerialNumber());  
            mRequestQueue.add(request);  
        } else {  
            Log.d("", "### 請求隊列中已經含有");  
        }  
    }  

    public void clear() {  
        mRequestQueue.clear();  
    }  

    public BlockingQueue> getAllRequests() {  
        return mRequestQueue;  
    }  

    /** 
     * 為每個請求生成一個系列號 
     *  
     * @return 序列號 
     */  
    private int generateSerialNumber() {  
        return mSerialNumGenerator.incrementAndGet();  
    }  
}

這裡引入了一個HttpStack,這是一個接口,只有一個函數。該接口定義了執行網絡請求的抽象,代碼如下:

/** 
 * 執行網絡請求的接口 
 *  
 * @author mrsimple 
 */  
public interface HttpStack {  
    /** 
     * 執行Http請求 
     *  
     * @param request 待執行的請求 
     * @return 
     */  
    public Response performRequest(Request request);  
}
 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved