教你寫Android網(wǎng)絡(luò)框架之Request、Response類與請求隊列
來源:程序員人生 發(fā)布時間:2015-01-30 08:14:59 閱讀次數(shù):4683次
轉(zhuǎn)載請注明出處,本文來自【 Mr.Simple的博客 】。
我正在參加博客之星,點擊這里投我1票吧,謝謝~
前言
在教你寫Android網(wǎng)絡(luò)框架之基本架構(gòu)1文中我們已介紹了SimpleNet網(wǎng)絡(luò)框架的基本結(jié)構(gòu),今天我們就開始從代碼的角度來開始切入該網(wǎng)絡(luò)框架的實現(xiàn),在剖析的同時我們會分析設(shè)計思路,和為何要這樣做,這樣做的好處是甚么。這樣我們不但學到了如何實現(xiàn)網(wǎng)絡(luò)框架,也會學到設(shè)計1個通用的框架應當有哪些斟酌,這就擴大到框架設(shè)計的范疇,通過這個簡單的實例希望能給新人1些幫助。固然這只是1家之言,大家都可以有自己的實現(xiàn)思路。
正如你所看到的,這系列博客是為新人準備的,如果你是高手,請疏忽。
在框架開發(fā)當中,很重要的1點就是抽象。也就是面向?qū)ο笾兄匾?條原則: 依賴顛倒原則,簡單來講就是要依賴抽象,而不依賴具體。這樣就使得我們的框架具有可擴大性,同時也滿足了開閉原則,即對擴大開放,對修改關(guān)閉。針對我們的網(wǎng)絡(luò)框架來講,最重要的抽象就是Reqeust類、Response類,因此今天我們就從兩個類開始切入。最后我們再引入網(wǎng)絡(luò)框架中的要求隊列(RequestQueue),這是SimpleNet中的中樞神經(jīng),所有的要求都需要放到該隊列,然后等待著被履行。要求隊列就像工廠中的流水線1樣,而網(wǎng)絡(luò)要求就像流水線上的待加工的產(chǎn)品。履行網(wǎng)絡(luò)要求的對象就類似工廠中的工人,在自己的崗位上等待流水線上傳遞過來的產(chǎn)品,然后對其加工,加工完就將產(chǎn)品放到其他的位置。它們角色對應關(guān)系參考圖1,如對SimpleNet的1些角色不太清楚可參考教你寫Android網(wǎng)絡(luò)框架之基本架構(gòu)1文。

圖1
Request類
既然網(wǎng)絡(luò)框架,那末我們先從網(wǎng)絡(luò)要求類開始。前文已說過,既然是框架,那末就需要可擴大性。因此注定了Request是抽象,而不是具體。而對網(wǎng)絡(luò)要求來講,用戶得到的要求結(jié)果格式是不肯定,比如有的服務器返回的是json,有的返回的是xml,有的直接是字符串。但是對Http Response來講,它的返回數(shù)據(jù)類型都是Stream,也就是我們得到的原始數(shù)據(jù)都是2進制的流。所以在Request基類中我們必須預留方法來解析Response返回的具體類型,雖然返回的類型不同,但是他們的處理邏輯是1樣的,因此我們可把Request作為泛型類,它的泛型類型就是它的返回數(shù)據(jù)類型,比如Request<String>,那末它的返回數(shù)據(jù)類型就是String類型的。另外還有要求的優(yōu)先級、可取消等,我們這里先給出核心代碼,然后再繼續(xù)分析。
/**
* 網(wǎng)絡(luò)要求類. 注意GET和DELETE不能傳遞要求參數(shù),由于其要求的性質(zhì)而至,用戶可以將參數(shù)構(gòu)建到url后傳遞進來到Request中.
*
* @author mrsimple
* @param <T> T為要求返回的數(shù)據(jù)類型
*/
public abstract class Request<T> implements Comparable<Request<T>> {
/**
* http要求方法枚舉,這里我們只有GET, POST, PUT, DELETE4種
*
* @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)先級枚舉
*
* @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⑻";
/**
* 要求序列號
*/
protected int mSerialNum = 0;
/**
* 優(yōu)先級默許設(shè)置為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>();
/**
* 要求參數(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ò)要求中解析結(jié)果,子類覆寫
*
* @param response
* @return
*/
public abstract T parseResponse(Response response);
/**
* 處理Response,該方法運行在UI線程.
*
* @param response
*/
public final void deliveryResponse(Response response) {
// 解析得到要求結(jié)果
T result = parseResponse(response);
if (mRequestListener != null) {
int stCode = response != null ? response.getStatusCode() : ⑴;
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參數(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);
}
}
// 用于對要求的排序處理,根據(jù)優(yōu)先級和加入到隊列的序號進行排序
@Override
public int compareTo(Request<T> another) {
Priority myPriority = this.getPriority();
Priority anotherPriority = another.getPriority();
// 如果優(yōu)先級相等,那末依照添加到隊列的序列號順序來履行
return myPriority.equals(another) ? this.getSerialNumber() - another.getSerialNumber()
: myPriority.ordinal() - anotherPriority.ordinal();
}
/**
* 網(wǎng)絡(luò)要求Listener,會被履行在UI線程
*
* @author mrsimple
* @param <T> 要求的response類型
*/
public static interface RequestListener<T> {
/**
* 要求完成的回調(diào)
*
* @param response
*/
public void onComplete(int stCode, T response, String errMsg);
}
}
上述代碼Request<T>為抽象類,T則為該要求Response的數(shù)據(jù)格式。這個T是要求類中的1個比較重要的點,不同的人有不同的需求,即要求Reponse的數(shù)據(jù)格式其實不是都是1樣的,我們必須斟酌到要求返回類型的多樣性,用泛型T來表示返回的數(shù)據(jù)格式類型,然后Request子類覆寫對應的方法實現(xiàn)解析Response的數(shù)據(jù)格式,最后調(diào)用要求Listener將要求結(jié)果履行在UI線程,這樣全部要求就完成了。
每一個Request都有1個序列號,該序列號由要求隊列生成,標識該要求在隊列中的序號,該序號和要求優(yōu)先級決定了該要求在隊列中的排序,即它在要求隊列的履行順序。每一個要求有要求方式,例如"POST"、"GET",這里我們用枚舉來代替,具名類型比單純的字符串更容易于使用。每一個Request都可以添加Header、Body參數(shù) ( 關(guān)于要求參數(shù)的格式可以參考 4種常見的 POST 提交數(shù)據(jù)方式),并且可以取消。抽象類封裝了通用的代碼,只有可變的部份是抽象函數(shù),這里只有parseResponse這個函數(shù)。
例如,我們返回的數(shù)據(jù)格式是Json,那末我們構(gòu)建1個子類叫做JsonRequest,示例代碼以下。
/**
* 返回的數(shù)據(jù)類型為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的結(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;
}
}
可以看到,實現(xiàn)1個要求類還是非常簡單的,只需要覆寫
parseResponse函數(shù)來解析你的要求返回的數(shù)據(jù)便可。這樣就保證了可擴大性,比如后面如果我想使用這個框架來做1個ImageLoader,那末我可以創(chuàng)建1個ImageRequest,該要求返回的類型就是Bitmap,那末我們只需要覆寫parseResponse函數(shù),然后把結(jié)果轉(zhuǎn)換成Bitmap便可。 這里引入了Response類,這個Response類存儲了要求的狀態(tài)碼、要求結(jié)果等內(nèi)容,我們繼續(xù)往下看。
Response類
每一個要求都對應1個Response,但這里的問題是這個Response的數(shù)據(jù)格式我們是不知道的。我們寫的是框架,不是利用。框架只是構(gòu)建1個基本環(huán)境,并且附帶1些比較經(jīng)常使用的類,比如這里的JsonRequest。但是重要的1點是可讓用戶自由、簡單的擴大以實現(xiàn)他的需求。對Response類來講,我們最重要的1點就是要肯定要求結(jié)果的數(shù)據(jù)格式類型。我們都知道,HTTP實際上是基于TCP協(xié)議,而TCP協(xié)議又是基于Socket,Socket實際上操作的也就是輸入、輸出流,輸出流是向服務器寫數(shù)據(jù),輸入流自然是從服務器讀取數(shù)據(jù)。因此我們在Response類中應當使用InputStream存儲結(jié)果或使用更加易于使用的字節(jié)數(shù)組,這里我們使用字節(jié)數(shù)組來存儲。我們來看Response類。
/**
* 要求結(jié)果類,繼承自BasicHttpResponse,將結(jié)果存儲在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,然后將輸入流轉(zhuǎn)換成字節(jié)數(shù)組,然后包裝了幾個經(jīng)常使用的方法,主要是為了使用簡單吧。我們將結(jié)果存儲為字節(jié)數(shù)組,這樣可以用戶可以很方便的將結(jié)果轉(zhuǎn)換為String、bitmap等數(shù)據(jù)類型,如果直接存儲的是InputStream,那末在很多時候用戶需要在外圍將InputStream先轉(zhuǎn)換為字節(jié)數(shù)組,然后再轉(zhuǎn)換為終究的格式,例如InputStream轉(zhuǎn)為String類型。這也是為何我們這里選用byte[]而不用InputStream的緣由。
要求隊列
網(wǎng)絡(luò)要求隊列也比較簡單,實際上就是內(nèi)部封裝了1個優(yōu)先級隊列,在構(gòu)建隊列時會啟動幾個NetworkExecutor ( 子線程 )來從要求隊列中獲得要求,并且履行要求。要求隊列會根據(jù)要求的優(yōu)先級進行排序,這樣就保證了1些優(yōu)先級高的要求得到盡快的處理,這也就是為何Request類中實現(xiàn)了Comparable接口的緣由。如果優(yōu)先級1致的情況下,則會根據(jù)要求加入到隊列的順序來排序,這個序號由要求隊列生成,這樣就保證了優(yōu)先級1樣的情況下依照FIFO的策略履行。
/**
* 要求隊列, 使用優(yōu)先隊列,使得要求可以依照優(yōu)先級進行處理. [ Thread Safe ]
*
* @author mrsimple
*/
public final class RequestQueue {
/**
* 要求隊列 [ Thread-safe ]
*/
private BlockingQueue<Request<?>> mRequestQueue = new PriorityBlockingQueue<Request<?>>();
/**
* 要求的序列化生成器
*/
private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);
/**
* 默許的核心數(shù)
*/
public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors() + 1;
/**
* CPU核心數(shù) + 1個分發(fā)線程數(shù)
*/
private int mDispatcherNums = DEFAULT_CORE_NUMS;
/**
* NetworkExecutor,履行網(wǎng)絡(luò)要求的線程
*/
private NetworkExecutor[] mDispatchers = null;
/**
* Http要求的真正履行者
*/
private HttpStack mHttpStack;
/**
* @param coreNums 線程核心數(shù)
*/
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;
}
/**
* 為每一個要求生成1個系列號
*
* @return 序列號
*/
private int generateSerialNumber() {
return mSerialNumGenerator.incrementAndGet();
}
}
這里引入了1個HttpStack,這是1個接口,只有1個函數(shù)。該接口定義了履行網(wǎng)絡(luò)要求的抽象,代碼以下:
/**
* 履行網(wǎng)絡(luò)要求的接口
*
* @author mrsimple
*/
public interface HttpStack {
/**
* 履行Http要求
*
* @param request 待履行的要求
* @return
*/
public Response performRequest(Request<?> request);
}
今天就先到這里吧,關(guān)于HttpStack、NetworkExecutor、ResponseDelivery的介紹將在下1篇博客中更新,敬請期待。
如果你看到這里都不給我投1篇,那簡直太不夠意思了!!點擊這里投我1票吧,謝謝~
Github地址
SimpleNet網(wǎng)絡(luò)框架地址
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學習有所幫助,可以手機掃描二維碼進行捐贈