教你寫Android網絡框架之Http請求的分發與執行
前言
在前兩篇( 教你寫Android網絡框架之基本架構 、 教你寫Android網絡框架之Request、Response類與請求隊列 )博客中,我們已經介紹了SimpleNet框架的基本結構,以及Request、Response、請求隊列的實現,以及為什么要這么設計,這么設計的 考慮是什么。前兩篇博客中已經介紹了各個角色,今天我們就來剖析另外幾個特別重要的角色,即NetworkExecutor、HttpStack以及 ResponseDelivery,它們分別對應的功能是網絡請求線程、Http執行器、Response分發,這三者是執行http請求和處理 Response的核心。
我們再來回顧一下,SimpleNet各個角色的分工合作。首先用戶需要創建一個請求隊列,然后將各個請求添加到請求隊列中。多個 NetworkExecutor ( 實質上是一個線程 )共享一個消息隊列,在各個NetworkExecutor中循環的取請求隊列中的請求,拿到一個請求,然后通過HttpStack來執行Http請求, 請求完成后最終通過ResponseDelivery將Response結果分發到UI線程,保證請求回調執行在UI線程,這樣用戶就可以直接在回調中更 新UI。執行流程如圖1.
圖1
還有不太了解這幅架構圖的可以參考專欄中的第一篇博客。
NetworkExecutor
作為SimpleNet中的“心臟”,NetworkExecutor起著非常重要的作用。之所以稱之為“心臟”,是由于 NetworkExecutor的功能是源源不斷地從請求隊列中獲取請求,然后交給HttpStack來執行。它就像汽車中的發動機,人體中的心臟一樣, 帶動著整個框架的運行。
NetworkExecutor實質上是一個Thread,在run方法中我們會執行一個循環,不斷地從請求隊列中取得請求,然后交給HttpStack,由于比較簡單我們直接上代碼吧。
/**
* 網絡請求Executor,繼承自Thread,從網絡請求隊列中循環讀取請求并且執行
*
* @author mrsimple
*/
final class NetworkExecutor extends Thread {
/**
* 網絡請求隊列
*/
private BlockingQueue<Request<?>> mRequestQueue;
/**
* 網絡請求棧
*/
private HttpStack mHttpStack;
/**
* 結果分發器,將結果投遞到主線程
*/
private static ResponseDelivery mResponseDelivery = new ResponseDelivery();
/**
* 請求緩存
*/
private static Cache<String, Response> mReqCache = new LruMemCache();
/**
* 是否停止
*/
private boolean isStop = false;
public NetworkExecutor(BlockingQueue<Request<?>> queue, HttpStack httpStack) {
mRequestQueue = queue;
mHttpStack = httpStack;
}
@Override
public void run() {
try {
while (!isStop) {
final Request<?> request = mRequestQueue.take();
if (request.isCanceled()) {
Log.d("### ", "### 取消執行了");
continue;
}
Response response = null;
if (isUseCache(request)) {
// 從緩存中取
response = mReqCache.get(request.getUrl());
} else {
// 從網絡上獲取數據
response = mHttpStack.performRequest(request);
// 如果該請求需要緩存,那么請求成功則緩存到mResponseCache中
if (request.shouldCache() && isSuccess(response)) {
mReqCache.put(request.getUrl(), response);
}
}
// 分發請求結果
mResponseDelivery.deliveryResponse(request, response);
}
} catch (InterruptedException e) {
Log.i("", "### 請求分發器退出");
}
}
private boolean isSuccess(Response response) {
return response != null && response.getStatusCode() == 200;
}
private boolean isUseCache(Request<?> request) {
return request.shouldCache() && mReqCache.get(request.getUrl()) != null;
}
public void quit() {
isStop = true;
interrupt();
}
}在啟動請求隊列時,我們會啟動指定數量的NetworkExecutor ( 參考 教你寫Android網絡框架之Request、Response類與請求隊列)。在構造NetworkExecutor時會將請求隊列以及 HttpStack注入進來,這樣NetworkExecutor就具有了兩大元素,即請求隊列和HttpStack。然后在run函數的循環中不斷地取 出請求,并且交給HttpStack執行,其間還會判斷該請求是否需要緩存、是否已經有緩存,如果使用緩存、并且已經含有緩存,那么則使用緩存的結果等。 在run函數中執行http請求,這樣就將網絡請求執行在子線程中。執行Http需要HttpStack,但最終我們需要將結果分發到UI線程需要 ResponseDelivery,下面我們挨個介紹。
HttpStack
HttpStack只是一個接口,只有一個performRequest函數,也就是執行請求。
/**
* 執行網絡請求的接口
*
* @author mrsimple
*/
public interface HttpStack {
/**
* 執行Http請求
*
* @param request 待執行的請求
* @return
*/
public Response performRequest(Request<?> request);
}HttpStack是網絡請求的真正執行者,有HttpClientStack和HttpUrlConnStack,兩者分別為Apache的 HttpClient和java的HttpURLConnection,關于這兩者的區別請參考:Android訪問網絡,使用 HttpURLConnection還是HttpClient? 默認情況下,我們會根據api版本來構建對應的HttpStack,當然用戶也可以自己實現一個HttpStack,然后通過SimpleNet的工廠函 數傳遞進來。例如 :
/**
* @param coreNums 線程核心數
* @param httpStack http執行器
*/
protected RequestQueue(int coreNums, HttpStack httpStack) {
mDispatcherNums = coreNums;
mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();
}
在購置請求隊列時會傳遞HttpStack,如果httpStack為空,則由HttpStackFactory根據api版本生成對應的HttpStack。即api 9以下是HttpClientStack, api 9 及其以上則為HttpUrlConnStack。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
/**
* 根據api版本選擇HttpClient或者HttpURLConnection
*
* @author mrsimple
*/
public final class HttpStackFactory {
private static final int GINGERBREAD_SDK_NUM = 9;
/**
* 根據SDK版本號來創建不同的Http執行器,即SDK 9之前使用HttpClient,之后則使用HttlUrlConnection,
* 兩者之間的差別請參考 :
* http://android-developers.blogspot.com/2011/09/androids-http-clients.html
*
* @return
*/
public static HttpStack createHttpStack() {
int runtimeSDKApi = Build.VERSION.SDK_INT;
if (runtimeSDKApi >= GINGERBREAD_SDK_NUM) {
return new HttpUrlConnStack();
}
return new HttpClientStack();
}
}HttpClientStack和HttpUrlConnStack分別就是封裝了HttpClient和HttpURLConnection的http 請求,構建請求、設置header、設置請求參數、解析Response等操作。針對于這一層,我們沒有給出一個抽象類,原因是HttpClient和 HttpURLConnection并不屬于同一個類族,他們的行為雖然都很相似,但是其中涉及到的一些類型卻是不同的。這里我們給出 HttpUrlConnStack的示例,最近比較忙,因此寫的配置比較簡單,有需要的同學自己優化了。
/**
* 使用HttpURLConnection執行網絡請求的HttpStack
*
* @author mrsimple
*/
public class HttpUrlConnStack implements HttpStack {
/**
* 配置Https
*/
HttpUrlConnConfig mConfig = HttpUrlConnConfig.getConfig();
@Override
public Response performRequest(Request<?> request) {
HttpURLConnection urlConnection = null;
try {
// 構建HttpURLConnection
urlConnection = createUrlConnection(request.getUrl());
// 設置headers
setRequestHeaders(urlConnection, request);
// 設置Body參數
setRequestParams(urlConnection, request);
// https 配置
configHttps(request);
return fetchResponse(urlConnection);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
private HttpURLConnection createUrlConnection(String url) throws IOException {
URL newURL = new URL(url);
URLConnection urlConnection = newURL.openConnection();
urlConnection.setConnectTimeout(mConfig.connTimeOut);
urlConnection.setReadTimeout(mConfig.soTimeOut);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
return (HttpURLConnection) urlConnection;
}
private void configHttps(Request<?> request) {
if (request.isHttps()) {
SSLSocketFactory sslFactory = mConfig.getSslSocketFactory();
// 配置https
if (sslFactory != null) {
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
HttpsURLConnection.setDefaultHostnameVerifier(mConfig.getHostnameVerifier());
}
}
}
private void setRequestHeaders(HttpURLConnection connection, Request<?> request) {
Set<String> headersKeys = request.getHeaders().keySet();
for (String headerName : headersKeys) {
connection.addRequestProperty(headerName, request.getHeaders().get(headerName));
}
}
protected void setRequestParams(HttpURLConnection connection, Request<?> request)
throws ProtocolException, IOException {
HttpMethod method = request.getHttpMethod();
connection.setRequestMethod(method.toString());
// add params
byte[] body = request.getBody();
if (body != null) {
// enable output
connection.setDoOutput(true);
// set content type
connection
.addRequestProperty(Request.HEADER_CONTENT_TYPE, request.getBodyContentType());
// write params data to connection
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.write(body);
dataOutputStream.close();
}
}
private Response fetchResponse(HttpURLConnection connection) throws IOException {
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
// 狀態行數據
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
// 構建response
Response response = new Response(responseStatus);
// 設置response數據
response.setEntity(entityFromURLConnwction(connection));
addHeadersToResponse(response, connection);
return response;
}
/**
* 執行HTTP請求之后獲取到其數據流,即返回請求結果的流
*
* @param connection
* @return
*/
private HttpEntity entityFromURLConnwction(HttpURLConnection connection) {
BasicHttpEntity entity = new BasicHttpEntity();
InputStream inputStream = null;
try {
inputStream = connection.getInputStream();
} catch (IOException e) {
e.printStackTrace();
inputStream = connection.getErrorStream();
}
// TODO : GZIP
entity.setContent(inputStream);
entity.setContentLength(connection.getContentLength());
entity.setContentEncoding(connection.getContentEncoding());
entity.setContentType(connection.getContentType());
return entity;
}
private void addHeadersToResponse(BasicHttpResponse response, HttpURLConnection connection) {
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
}
}代碼很簡單,就不多說了。
ResponseDelivery
在HttpStack的performRequest函數中,我們會返回一個Response對象,該對象包含了我們請求對應的 Response。關于Response類你不太了解的可以參考教你寫Android網絡框架之Request、Response類與請求隊列。我們在 NetworkExecutor中執行http請求的最后一步會將結果分發給UI線程,主要工作其實就是將請求的回調執行到UI線程,以便用戶可以更新 UI等操作。
@Override
public void run() {
try {
while (!isStop) {
final Request<?> request = mRequestQueue.take();
if (request.isCanceled()) {
Log.d("### ", "### 取消執行了");
continue;
}
Response response = null;
if (isUseCache(request)) {
// 從緩存中取
response = mReqCache.get(request.getUrl());
} else {
// 從網絡上獲取數據
response = mHttpStack.performRequest(request);
// 如果該請求需要緩存,那么請求成功則緩存到mResponseCache中
if (request.shouldCache() && isSuccess(response)) {
mReqCache.put(request.getUrl(), response);
}
}
// 分發請求結果
mResponseDelivery.deliveryResponse(request, response);
}
} catch (InterruptedException e) {
Log.i("", "### 請求分發器退出");
}
}不管是從緩存中獲取還是從網絡上獲取,我們得到的都是一個Response對象,最后我們通過ResponseDelivery對象將結果分發給UI線程。
ResponseDelivery其實就是封裝了關聯了UI線程消息隊列的Handler,在deliveryResponse函數中將request的 deliveryResponse執行在UI線程中。既然我們有了關聯了UI線程的Handler對象,那么直接構建一個Runnable,在該 Runnable中執行request的deliveryResponse函數即可。在Request類的deliveryResponse中,又會調用 parseResponse解析Response結果,返回的結果類型就是Request
中的T,這個T是在Request子類中指定,例如JsonRequest,那么返回的Response的結果就是 JSONObject。這樣我們就得到了服務器返回的json數據,并且將這個json結果通過回調的形式傳遞給了UI線程。用戶就可以在該回調中更新 UI了。
這其中主要就是抽象和泛型,寫框架很多時候泛型是很重要的手段,因此熟悉使用抽象和泛型是面向對象開發的重要一步。
ResponseDelivery代碼如下 :
/**
* 請求結果投遞類,將請求結果投遞給UI線程
*
* @author mrsimple
*/
class ResponseDelivery implements Executor {
/**
* 主線程的hander
*/
Handler mResponseHandler = new Handler(Looper.getMainLooper());
/**
* 處理請求結果,將其執行在UI線程
*
* @param request
* @param response
*/
public void deliveryResponse(final Request<?> request, final Response response) {
Runnable respRunnable = new Runnable() {
@Override
public void run() {
request.deliveryResponse(response);
}
};
execute(respRunnable);
}
@Override
public void execute(Runnable command) {
mResponseHandler.post(command);
}
}Request類的deliveryResponse函數。
/**
* 處理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);
}
}這樣,整個請求過程就完成了。下面我們總結一下這個過程。
不同用戶的服務器返回的數據格式是不一致的,因此我們定義了Request 泛型基類,泛型T就是返回的數據格式類型。比如返回的數據格式為json,那對應的請求就是JsonRequest,泛型T為JSONObject,在 JsonRequest中覆寫parseResponse函數,將得到的Response中的原始數據轉換成JSONObject。然后將請求放到隊列 中,NetworkExecutor將請求分發給HttpStack執行,執行完成之后得到Response對象,最終ResponseDelivery 將結果通過請求回調投遞到UI線程。