前言
在教你写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 为抽象类,T则为该请求Response的数据格式。这个T是请求类中的一个比较重要的点,不同的人有不同的需求,即请求Reponse的数据格式并不是都是一样的,我们必须考虑到请求返回类型的多样性,用泛型T来表示返回的数据格式类型,然后Request子类覆写对应的方法实现解析Response的数据格式,最后调用请求Listener将请求结果执行在UI线程,这样整个请求就完成了。
每个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的介绍将在下一篇博客中更新,敬请期待。