轉(zhuǎn)載請(qǐng)注明出處,本文來(lái)自【 Mr.Simple的博客 】。
我正在參加博客之星,點(diǎn)擊這里投我一票吧,謝謝~
前言
在教你寫(xiě)Android網(wǎng)絡(luò)框架之基本架構(gòu)一文中我們已經(jīng)介紹了SimpleNet網(wǎng)絡(luò)框架的基本結(jié)構(gòu),今天我們就開(kāi)始從代碼的角度來(lái)開(kāi)始切入該網(wǎng)絡(luò)框架的實(shí)現(xiàn),在剖析的同時(shí)我們會(huì)分析設(shè)計(jì)思路,以及為什么要這樣做,這樣做的好處是什么。這樣我們不僅學(xué)到了如何實(shí)現(xiàn)網(wǎng)絡(luò)框架,也會(huì)學(xué)到設(shè)計(jì)一個(gè)通用的框架應(yīng)該有哪些考慮,這就擴(kuò)展到框架設(shè)計(jì)的范疇,通過(guò)這個(gè)簡(jiǎn)單的實(shí)例希望能給新人一些幫助。當(dāng)然這只是一家之言,大家都可以有自己的實(shí)現(xiàn)思路。
正如你所看到的,這系列博客是為新人準(zhǔn)備的,如果你是高手,請(qǐng)忽略。
在框架開(kāi)發(fā)當(dāng)中,很重要的一點(diǎn)就是抽象。也就是面向?qū)ο笾兄匾囊粭l原則: 依賴倒置原則,簡(jiǎn)單來(lái)說(shuō)就是要依賴抽象,而不依賴具體。這樣就使得我們的框架具有可擴(kuò)展性,同時(shí)也滿足了開(kāi)閉原則,即對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。針對(duì)于我們的網(wǎng)絡(luò)框架來(lái)說(shuō),最重要的抽象就是Reqeust類、Response類,因此今天我們就從兩個(gè)類開(kāi)始切入。最后我們?cè)僖刖W(wǎng)絡(luò)框架中的請(qǐng)求隊(duì)列(RequestQueue),這是SimpleNet中的中樞神經(jīng),所有的請(qǐng)求都需要放到該隊(duì)列,然后等待著被執(zhí)行。請(qǐng)求隊(duì)列就像工廠中的流水線一樣,而網(wǎng)絡(luò)請(qǐng)求就像流水線上的待加工的產(chǎn)品。執(zhí)行網(wǎng)絡(luò)請(qǐng)求的對(duì)象就類似工廠中的工人,在自己的崗位上等待流水線上傳遞過(guò)來(lái)的產(chǎn)品,然后對(duì)其加工,加工完就將產(chǎn)品放到其他的位置。它們角色對(duì)應(yīng)關(guān)系參考圖1,如對(duì)SimpleNet的一些角色不太清楚可參考教你寫(xiě)Android網(wǎng)絡(luò)框架之基本架構(gòu)一文。
圖1
Request類
既然網(wǎng)絡(luò)框架,那么我們先從網(wǎng)絡(luò)請(qǐng)求類開(kāi)始。前文已經(jīng)說(shuō)過(guò),既然是框架,那么就需要可擴(kuò)展性。因此注定了Request是抽象,而不是具體。而對(duì)于網(wǎng)絡(luò)請(qǐng)求來(lái)說(shuō),用戶得到的請(qǐng)求結(jié)果格式是不確定,比如有的服務(wù)器返回的是json,有的返回的是xml,有的直接是字符串。但是對(duì)于Http Response來(lái)說(shuō),它的返回?cái)?shù)據(jù)類型都是Stream,也就是我們得到的原始數(shù)據(jù)都是二進(jìn)制的流。所以在Request基類中我們必須預(yù)留方法來(lái)解析Response返回的具體類型,雖然返回的類型不同,但是他們的處理邏輯是一樣的,因此我們可把Request作為泛型類,它的泛型類型就是它的返回?cái)?shù)據(jù)類型,比如Request<String>,那么它的返回?cái)?shù)據(jù)類型就是String類型的。另外還有請(qǐng)求的優(yōu)先級(jí)、可取消等,我們這里先給出核心代碼,然后再繼續(xù)分析。
- /**
- * 網(wǎng)絡(luò)請(qǐng)求類. 注意GET和DELETE不能傳遞請(qǐng)求參數(shù),因?yàn)槠湔?qǐng)求的性質(zhì)所致,用戶可以將參數(shù)構(gòu)建到url后傳遞進(jìn)來(lái)到Request中.
- *
- * @author mrsimple
- * @param <T> T為請(qǐng)求返回的數(shù)據(jù)類型
- */
- public abstract class Request<T> implements Comparable<Request<T>> {
-
- /**
- * http請(qǐng)求方法枚舉,這里我們只有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;
- }
- }
-
- /**
- * 優(yōu)先級(jí)枚舉
- *
- * @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";
- /**
- * 請(qǐng)求序列號(hào)
- */
- protected int mSerialNum = 0;
- /**
- * 優(yōu)先級(jí)默認(rèn)設(shè)置為Normal
- */
- protected Priority mPriority = Priority.NORMAL;
- /**
- * 是否取消該請(qǐng)求
- */
- protected boolean isCancel = false;
-
- /** 該請(qǐng)求是否應(yīng)該緩存 */
- private boolean mShouldCache = true;
- /**
- * 請(qǐng)求Listener
- */
- protected RequestListener<T> mRequestListener;
- /**
- * 請(qǐng)求的url
- */
- private String mUrl = "";
- /**
- * 請(qǐng)求的方法
- */
- HttpMethod mHttpMethod = HttpMethod.GET;
-
- /**
- * 請(qǐng)求的header
- */
- private Map<String, String> mHeaders = new HashMap<String, String>();
- /**
- * 請(qǐng)求參數(shù)
- */
- 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;
- }
-
- /**
- * 從原生的網(wǎng)絡(luò)請(qǐng)求中解析結(jié)果,子類覆寫(xiě)
- *
- * @param response
- * @return
- */
- public abstract T parseResponse(Response response);
-
- /**
- * 處理Response,該方法運(yùn)行在UI線程.
- *
- * @param response
- */
- public final void deliveryResponse(Response response) {
- // 解析得到請(qǐng)求結(jié)果
- 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請(qǐng)求時(shí)的Body參數(shù)字節(jié)數(shù)組
- *
- */
- public byte[] getBody() {
- Map<String, String> params = getParams();
- if (params != null && params.size() > 0) {
- return encodeParameters(params, getParamsEncoding());
- }
- return null;
- }
-
- /**
- * 將參數(shù)轉(zhuǎn)換為Url編碼的參數(shù)串
- */
- 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);
- }
- }
-
- // 用于對(duì)請(qǐng)求的排序處理,根據(jù)優(yōu)先級(jí)和加入到隊(duì)列的序號(hào)進(jìn)行排序
- @Override
- public int compareTo(Request<T> another) {
- Priority myPriority = this.getPriority();
- Priority anotherPriority = another.getPriority();
- // 如果優(yōu)先級(jí)相等,那么按照添加到隊(duì)列的序列號(hào)順序來(lái)執(zhí)行
- return myPriority.equals(another) ? this.getSerialNumber() - another.getSerialNumber()
- : myPriority.ordinal() - anotherPriority.ordinal();
- }
-
-
-
- /**
- * 網(wǎng)絡(luò)請(qǐng)求Listener,會(huì)被執(zhí)行在UI線程
- *
- * @author mrsimple
- * @param <T> 請(qǐng)求的response類型
- */
- public static interface RequestListener<T> {
- /**
- * 請(qǐng)求完成的回調(diào)
- *
- * @param response
- */
- public void onComplete(int stCode, T response, String errMsg);
- }
- }
上述代碼Request<T>為抽象類,T則為該請(qǐng)求Response的數(shù)據(jù)格式。這個(gè)T是請(qǐng)求類中的一個(gè)比較重要的點(diǎn),不同的人有不同的需求,即請(qǐng)求Reponse的數(shù)據(jù)格式并不是都是一樣的,我們必須考慮到請(qǐng)求返回類型的多樣性,用泛型T來(lái)表示返回的數(shù)據(jù)格式類型,然后Request子類覆寫(xiě)對(duì)應(yīng)的方法實(shí)現(xiàn)解析Response的數(shù)據(jù)格式,最后調(diào)用請(qǐng)求Listener將請(qǐng)求結(jié)果執(zhí)行在UI線程,這樣整個(gè)請(qǐng)求就完成了。
每個(gè)Request都有一個(gè)序列號(hào),該序列號(hào)由請(qǐng)求隊(duì)列生成,標(biāo)識(shí)該請(qǐng)求在隊(duì)列中的序號(hào),該序號(hào)和請(qǐng)求優(yōu)先級(jí)決定了該請(qǐng)求在隊(duì)列中的排序,即它在請(qǐng)求隊(duì)列的執(zhí)行順序。每個(gè)請(qǐng)求有請(qǐng)求方式,例如"POST"、"GET",這里我們用枚舉來(lái)代替,具名類型比單純的字符串更易于使用。每個(gè)Request都可以添加Header、Body參數(shù) ( 關(guān)于請(qǐng)求參數(shù)的格式可以參考 四種常見(jiàn)的 POST 提交數(shù)據(jù)方式),并且可以取消。抽象類封裝了通用的代碼,只有可變的部分是抽象函數(shù),這里只有parseResponse這個(gè)函數(shù)。
例如,我們返回的數(shù)據(jù)格式是Json,那么我們構(gòu)建一個(gè)子類叫做JsonRequest,示例代碼如下。
- /**
- * 返回的數(shù)據(jù)類型為Json的請(qǐng)求, Json對(duì)應(yīng)的對(duì)象類型為JSONObject
- *
- * @author mrsimple
- */
- public class JsonRequest extends Request<JSONObject> {
-
- public JsonRequest(HttpMethod method, String url, RequestListener<JSONObject> listener) {
- super(method, url, listener);
- }
-
-
- /**
- * 將Response的結(jié)果轉(zhuǎn)換為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;
- }
- }
可以看到,實(shí)現(xiàn)一個(gè)請(qǐng)求類還是非常簡(jiǎn)單的,只需要覆寫(xiě)
parseResponse函數(shù)來(lái)解析你的請(qǐng)求返回的數(shù)據(jù)即可。這樣就保證了可擴(kuò)展性,比如后面如果我想使用這個(gè)框架來(lái)做一個(gè)ImageLoader,那么我可以創(chuàng)建一個(gè)ImageRequest,該請(qǐng)求返回的類型就是Bitmap,那么我們只需要覆寫(xiě)parseResponse函數(shù),然后把結(jié)果轉(zhuǎn)換成Bitmap即可。 這里引入了Response類,這個(gè)Response類存儲(chǔ)了請(qǐng)求的狀態(tài)碼、請(qǐng)求結(jié)果等內(nèi)容,我們繼續(xù)往下看。
Response類
每個(gè)請(qǐng)求都對(duì)應(yīng)一個(gè)Response,但這里的問(wèn)題是這個(gè)Response的數(shù)據(jù)格式我們是不知道的。我們寫(xiě)的是框架,不是應(yīng)用??蚣苤皇菢?gòu)建一個(gè)基本環(huán)境,并且附帶一些比較常用的類,比如這里的JsonRequest。但是重要的一點(diǎn)是可以讓用戶自由、簡(jiǎn)單的擴(kuò)展以實(shí)現(xiàn)他的需求。對(duì)于Response類來(lái)說(shuō),我們最重要的一點(diǎn)就是要確定請(qǐng)求結(jié)果的數(shù)據(jù)格式類型。我們都知道,HTTP實(shí)際上是基于TCP協(xié)議,而TCP協(xié)議又是基于Socket,Socket實(shí)際上操作的也就是輸入、輸出流,輸出流是向服務(wù)器寫(xiě)數(shù)據(jù),輸入流自然是從服務(wù)器讀取數(shù)據(jù)。因此我們?cè)赗esponse類中應(yīng)該使用InputStream存儲(chǔ)結(jié)果或者使用更為易于使用的字節(jié)數(shù)組,這里我們使用字節(jié)數(shù)組來(lái)存儲(chǔ)。我們來(lái)看Response類。
- /**
- * 請(qǐng)求結(jié)果類,繼承自BasicHttpResponse,將結(jié)果存儲(chǔ)在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];
- }
-
- }
這個(gè)類很簡(jiǎn)單,只是繼承了BasicHttpResponse,然后將輸入流轉(zhuǎn)換成字節(jié)數(shù)組,然后包裝了幾個(gè)常用的方法,主要是為了使用簡(jiǎn)單吧。我們將結(jié)果存儲(chǔ)為字節(jié)數(shù)組,這樣可以用戶可以很方便的將結(jié)果轉(zhuǎn)換為String、bitmap等數(shù)據(jù)類型,如果直接存儲(chǔ)的是InputStream,那么在很多時(shí)候用戶需要在外圍將InputStream先轉(zhuǎn)換為字節(jié)數(shù)組,然后再轉(zhuǎn)換為最終的格式,例如InputStream轉(zhuǎn)為String類型。這也是為什么我們這里選用byte[]而不用InputStream的原因。
請(qǐng)求隊(duì)列
網(wǎng)絡(luò)請(qǐng)求隊(duì)列也比較簡(jiǎn)單,實(shí)際上就是內(nèi)部封裝了一個(gè)優(yōu)先級(jí)隊(duì)列,在構(gòu)建隊(duì)列時(shí)會(huì)啟動(dòng)幾個(gè)NetworkExecutor ( 子線程 )來(lái)從請(qǐng)求隊(duì)列中獲取請(qǐng)求,并且執(zhí)行請(qǐng)求。請(qǐng)求隊(duì)列會(huì)根據(jù)請(qǐng)求的優(yōu)先級(jí)進(jìn)行排序,這樣就保證了一些優(yōu)先級(jí)高的請(qǐng)求得到盡快的處理,這也就是為什么Request類中實(shí)現(xiàn)了Comparable接口的原因。如果優(yōu)先級(jí)一致的情況下,則會(huì)根據(jù)請(qǐng)求加入到隊(duì)列的順序來(lái)排序,這個(gè)序號(hào)由請(qǐng)求隊(duì)列生成,這樣就保證了優(yōu)先級(jí)一樣的情況下按照FIFO的策略執(zhí)行。
- /**
- * 請(qǐng)求隊(duì)列, 使用優(yōu)先隊(duì)列,使得請(qǐng)求可以按照優(yōu)先級(jí)進(jìn)行處理. [ Thread Safe ]
- *
- * @author mrsimple
- */
- public final class RequestQueue {
- /**
- * 請(qǐng)求隊(duì)列 [ Thread-safe ]
- */
- private BlockingQueue<Request<?>> mRequestQueue = new PriorityBlockingQueue<Request<?>>();
- /**
- * 請(qǐng)求的序列化生成器
- */
- private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);
-
- /**
- * 默認(rèn)的核心數(shù)
- */
- public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors() + 1;
- /**
- * CPU核心數(shù) + 1個(gè)分發(fā)線程數(shù)
- */
- private int mDispatcherNums = DEFAULT_CORE_NUMS;
- /**
- * NetworkExecutor,執(zhí)行網(wǎng)絡(luò)請(qǐng)求的線程
- */
- private NetworkExecutor[] mDispatchers = null;
- /**
- * Http請(qǐng)求的真正執(zhí)行者
- */
- private HttpStack mHttpStack;
-
- /**
- * @param coreNums 線程核心數(shù)
- */
- protected RequestQueue(int coreNums, HttpStack httpStack) {
- mDispatcherNums = coreNums;
- mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();
- }
-
- /**
- * 啟動(dòng)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();
- }
- }
- }
-
- /**
- * 不能重復(fù)添加請(qǐng)求
- *
- * @param request
- */
- public void addRequest(Request<?> request) {
- if (!mRequestQueue.contains(request)) {
- request.setSerialNumber(this.generateSerialNumber());
- mRequestQueue.add(request);
- } else {
- Log.d("", "### 請(qǐng)求隊(duì)列中已經(jīng)含有");
- }
- }
-
- public void clear() {
- mRequestQueue.clear();
- }
-
- public BlockingQueue<Request<?>> getAllRequests() {
- return mRequestQueue;
- }
-
- /**
- * 為每個(gè)請(qǐng)求生成一個(gè)系列號(hào)
- *
- * @return 序列號(hào)
- */
- private int generateSerialNumber() {
- return mSerialNumGenerator.incrementAndGet();
- }
- }
這里引入了一個(gè)HttpStack,這是一個(gè)接口,只有一個(gè)函數(shù)。該接口定義了執(zhí)行網(wǎng)絡(luò)請(qǐng)求的抽象,代碼如下:
- /**
- * 執(zhí)行網(wǎng)絡(luò)請(qǐng)求的接口
- *
- * @author mrsimple
- */
- public interface HttpStack {
- /**
- * 執(zhí)行Http請(qǐng)求
- *
- * @param request 待執(zhí)行的請(qǐng)求
- * @return
- */
- public Response performRequest(Request<?> request);
- }
今天就先到這里吧,關(guān)于HttpStack、NetworkExecutor、ResponseDelivery的介紹將在下一篇博客中更新,敬請(qǐng)期待。
如果你看到這里都不給我投一篇,那簡(jiǎn)直太不夠意思了!!點(diǎn)擊這里投我一票吧,謝謝~
Github地址
SimpleNet網(wǎng)絡(luò)框架地址