1. 程式人生 > >教你寫Android網路框架之Request Response類與請求佇列

教你寫Android網路框架之Request Response類與請求佇列

               

我正在參加部落格之星,點選這裡投我一票吧,謝謝~   

前言

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

正如你所看到的,這系列部落格是為新人準備的,如果你是高手,請忽略。

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

教你寫Android網路框架之基本架構一文。

     

   圖1

Request類

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

/** * 網路請求類. 注意GET和DELETE不能傳遞請求引數,因為其請求的性質所致,使用者可以將引數構建到url後傳遞進來到Request中. *  * @author mrsimple * @param <T> T為請求返回的資料型別 */public abstract class Request<T> implements Comparable<Request<T>> {    /**     * 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<T> mRequestListener;    /**     * 請求的url     */    private String mUrl = "";    /**     * 請求的方法     */    HttpMethod mHttpMethod = HttpMethod.GET;    /**     * 請求的header     */    private Map<String, String> mHeaders = new HashMap<String, String>();    /**     * 請求引數     */    private Map<String, String> mBodyParams = new HashMap<String, String>();    /**     * @param method     * @param url     * @param listener     */    public Request(HttpMethod method, String url, RequestListener<T> 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 String getUrl() {        return mUrl;    }    public int getSerialNumber() {        return mSerialNum;    }    public void setSerialNumber(int mSerialNum) {        this.mSerialNum = mSerialNum;    }    public Priority getPriority() {        return mPriority;    }    public void setPriority(Priority mPriority) {        this.mPriority = mPriority;    }    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<String, String> getHeaders() {        return mHeaders;    }    public Map<String, String> getParams() {        return mBodyParams;    }    public void cancel() {        isCancel = true;    }    public boolean isCanceled() {        return isCancel;    }    /**     * 返回POST或者PUT請求時的Body引數位元組陣列     *     */    public byte[] getBody() {        Map<String, String> params = getParams();        if (params != null && params.size() > 0) {            return encodeParameters(params, getParamsEncoding());        }        return null;    }    /**     * 將引數轉換為Url編碼的引數串     */    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {        StringBuilder encodedParams = new StringBuilder();        try {            for (Map.Entry<String, String> 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<T> 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 <T> 請求的response型別     */    public static interface RequestListener<T> {        /**         * 請求完成的回撥         *          * @param response         */        public void onComplete(int stCode, T response, String errMsg);    }}
上述程式碼Request<T>為抽象類,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<JSONObject> {    public JsonRequest(HttpMethod method, String url, RequestListener<JSONObject> 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的策略執行。

/** * 請求佇列, 使用優先佇列,使得請求可以按照優先順序進行處理. [ Thread Safe ] *  * @author mrsimple */public final class RequestQueue {    /**     * 請求佇列 [ Thread-safe ]     */    private BlockingQueue<Request<?>> mRequestQueue = new PriorityBlockingQueue<Request<?>>();    /**     * 請求的序列化生成器     */    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<Request<?>> 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);}
今天就先到這裡吧,關於HttpStack、NetworkExecutor、ResponseDelivery的介紹將在下一篇部落格中更新,敬請期待。

 如果你看到這裡都不給我投一篇,那簡直太不夠意思了!點選這裡投我一票吧,謝謝~   

Github地址