教你写Android网络框架之Request、Response类与请求队列
前言
在教你写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
每个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的介绍将在下一篇博客中更新,敬请期待。