教你寫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類型的。另外還有請求的優先級、可取消等,我們這裏先給出核心代碼,然後再繼續分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | /** * 網絡請求類. 注意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>(); 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 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<String, String> getHeaders() { return mHeaders; } public Map<String, String> getParams() { return mBodyParams; } /** * 返回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
每個Request都有一個序列號,該序列號由請求隊列生成,標識該請求在隊列中的序號,該序號和請求優先級決定了該請求在隊列中的排序,即它在請求隊列的執行順序。每個請求有請求方式,例如”POST”、”GET”,這裏我們用枚舉來代替,具名類型比單純的字符串更易於使用。每個Request都可以添加Header、Body參數 ( 關於請求參數的格式可以參考 四種常見的 POST 提交數據方式),並且可以取消。抽象類封裝了通用的代碼,只有可變的部分是抽象函數,這裏只有parseResponse這個函數。
例如,我們返回的數據格式是Json,那麼我們構建一個子類叫做JsonRequest,示例代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/**
* 返回的數據類型爲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類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * 請求結果類,繼承自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的原因。
請求隊列
1
2
|
網絡請求隊列也比較簡單,實際上就是內部封裝了一個優先級隊列,在構建隊列時會啓動幾個NetworkExecutor
(
子線程
)來從請求隊列中獲取請求,並且執行請求。請求隊列會根據請求的優先級進行排序,這樣就保證了一些優先級高的請求得到儘快的處理,這也就是爲什麼Request類中實現了Comparable接口的原因。如果優先級一致的情況下,則會根據請求加入到隊列的順序來排序,這個序號由請求隊列生成,這樣就保證了優先級一樣的情況下按照FIFO的策略執行。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | /** * 請求隊列, 使用優先隊列,使得請求可以按照優先級進行處理. * * @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,這是一個接口,只有一個函數。該接口定義了執行網絡請求的抽象,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 執行網絡請求的接口
*
* @author mrsimple
*/
public
interface
HttpStack
{
/**
* 執行Http請求
*
* @param request 待執行的請求
* @return
*/
public
Response
performRequest(Request<?>
request);
}
|
沒錯就是仿照Volley實現的教學簡化版,今天就先到這裏吧。關於HttpStack、NetworkExecutor、ResponseDelivery的介紹將在下一篇博客中更新,敬請期待。