教你寫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,由於比較簡單我們直接上代碼吧。
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 | /** * 網絡請求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函數,也就是執行請求。
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);
}
|
HttpStack是網絡請求的真正執行者,有HttpClientStack和HttpUrlConnStack,兩者分別爲Apache的HttpClient和java的HttpURLConnection,關於這兩者的區別請參考:Android訪問網絡,使用HttpURLConnection還是HttpClient? 默認情況下,我們會根據api版本來構建對應的HttpStack,當然用戶也可以自己實現一個HttpStack,然後通過SimpleNet的工廠函數傳遞進來。
例如 :
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 | /** * @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的示例,最近比較忙,因此寫的配置比較簡單,有需要的同學自己優化了。
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
|
/**
* 使用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等操作。
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 | @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
這其中主要就是抽象和泛型,寫框架很多時候泛型是很重要的手段,因此熟悉使用抽象和泛型是面向對象開發的重要一步。
ResponseDelivery代碼如下 :
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
|
/**
* 請求結果投遞類,將請求結果投遞給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函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* 處理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線程。