版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003
现在市场的Http框架很多,比如我们熟知的NoHttp、Retrofit、Volley、android-async-http等上层框架,HttpURLConnection、OkHttp、HttpClient等底层框架,今天不说孰好孰坏,今天我带大家来分析NoHttp的源码,教大家如何来看NoHttp的源码。
今天的源码是以NoHttp1.0.5版及以上版本为基础的,今天我们由浅到深,深入浅出的分析第一个模块:请求模块。
NoHttp开源地址:https://github.com/yanzhenjie/NoHttp,。
更多NoHttp相关文章请看NoHttp博客专栏:http://blog.csdn.net/column/details/nohttp.html。
OkHttp做为NoHttp底层的博客详解:http://blog.csdn.net/yanzhenjie1003/article/details/52439317。
想要看一个框架的源码,首先就要学会怎么用,最起码基础用法要会,也不必深入或者精通。那么我们先来看下NoHttp的异步请求和同步请求的简单用法。
作为一个优秀的网络框架,必定时提供了异步请求、同步请求两种方式给开发者的。
NoHttp的异步,我们先来看一个最简单实践:
RequestQueue queue = NoHttp.newRequestQueue();
Request<String> sReq = new StringRequest(url, POST);
...
queue.add(what sReq, new OnResponseListener() {
public void onStart(int what){}
public void onFinish(int what){}
public void onSucceed(int what, Response<String> response){}
public void onFailed(int what, Response<String> response){}
});
来做个简单的分析,首先是new了一个队列,然后创建了一个String的Request,之后把这个请求添加到队列,等待请求网络后响应,完事儿,就是这么简单。
NoHttp同步请求,再来看一个最简单实例:
Request<String> request = new StringRequest(url, GET);
Response<String> response = NoHttp.startRequestSync(request);
这个更简单了,创建了一个请求,然后调用NoHttp同步请求的方法拿到请求结果。
看了异步请求和同步请求后我们来分析一下,应该从哪里开始看NoHttp的源码。
从上面的代码中看到,异步请求和同步请求最大的区别是:异步请求需要用队列发送,用Listener
接受请求结果,而同步请求是直接请求到结果,那么最大的可能就是异步请求是建立在同步请求这个基础上的。所以我们直接看异步请求,在开始之前我已经画好了几个图。
这个图是NoHttp异步请求时,从主线程调用网络到拿到结果的流程图,这里非常有必要对这些类做个说明。
名称 | 类型 | 默认实现类 | 说明 |
---|---|---|---|
RequestQueue | 类 | - | 请求队列 |
RequestDispatcher | 类 | - | 轮训队列的线程 |
IRestParser | 接口 | RestParser | 解析网络结果,针对IParserRequest |
IRestProtocol | 接口 | RestProtocol | 按照Http协议请求网络处理请求结果,针对IProtocolRequest |
IRestConnection | 接口 | RestConnection | 访问网络,针对IBaseRequst,更改底层为OkHttp时只需要实现它 |
IBaseRequst | 接口 | BaseRequest | 提供通用的网络设置、参数设置、Cookie、重试机制,下载和请求模块通用 |
IProtocolRequest | 接口 | ProtocolRequest | 请求模块的协议,例如缓存机制 |
IParserRequest | 接口 | ParserRequest | 泛型,提供解析请求结果ByteArray为泛型 |
Request | 接口 | RestRequest | 提供异步请求的参数记录功能,如what、Listener等 |
注意:其中IRestConnection
是网络访问的接口类,所以替换底层为OkHttp时,仅仅需要实现它就Ok了。
如果上面的图还是不够显然,我们可以结合下面的图再看一边动态流程图就更加显而易懂了:
从上面的两张图可以看到,任何一个请求都是从主线程开始发起,先把Request添加到RequestQueue
(队列)中,队列此时会启动子线程,再由ReuqestDispatcher
(请求分发者)把队列中请求按照一定的顺序分发给子线程去执行网络操作,ReuqestDispatcher
拿到结果后,用Handler
发送到主线程,这个过程就是NoHttp的整个异步请求的过程。
上面是大致的逻辑说明,接着上面的分析,我们来个总分总,现在我们现在开始撸源码,把各个环节的细节给拆开了,那么先入手的入手的就是RequestQueue
了。
我们先来看看NoHttp怎么创建RequestQueue
:
public static RequestQueue newRequestQueue() {
return newRequestQueue(DEFAULT_REQUEST_THREAD_SIZE);
}
public static RequestQueue newRequestQueue(int threadPoolSize) {
return newRequestQueue(DiskCacheStore.INSTANCE, RestConnection.getInstance(), threadPoolSize);
}
public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, IRestConnection connection, int threadPoolSize) {
return newRequestQueue(RestProtocol.getInstance(cache, connection), threadPoolSize);
}
public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}
public static RequestQueue newRequestQueue(IRestParser implRestParser, int threadPoolSize) {
RequestQueue requestQueue = new RequestQueue(implRestParser, threadPoolSize);
requestQueue.start();
return requestQueue;
}
这里有5个方法可以创建ReuqestQueue
,但是每个方法最终都会来到最后方法,最后一个方法需要两个参数,第一个参数是IRestParser
,也就是响应解析者,第二个参数是threadPoolSize
,也就是队列中启动的线程的数量,创建好ReuqestQueue
后,调用start()
方法启动队列,这时就可以往队列中添加请求了。
那么ReuqestQueue
中究竟是怎么处理Request
的呢?到这里我们就必须去看看ReuqestQueue
的源码了,我已经把原文中的英文注释改为中文了,方便大家理解:
public class RequestQueue {
/** * 保存没有完成的请求,包括正在执行的请求。 */
private final BlockingQueue<Request<?>> mUnFinishQueue;
/** * 保存没有执行的请求,不包括正在执行的请求。 */
private final BlockingQueue<Request<?>> mRequestQueue;
/** * Http 请求结果解析器。 */
private final IRestParser mImplRestParser;
/** * 线程池。 */
private RequestDispatcher[] mDispatchers;
public RequestQueue(IRestParser implRestParser, int threadPoolSize) {
mImplRestParser = implRestParser;
mDispatchers = new RequestDispatcher[threadPoolSize];
}
/** * 启动线程池,轮训请求队列。 */
public void start() {
stop();
for (int i = 0; i < mDispatchers.length; i++) {
RequestDispatcher networkDispatcher = new RequestDispatcher(..., mImplRestParser);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
/** * 1. 添加一个请求到队列,如果队列中请求数没有满,则会立即执行,否则等待前面的请求执行完成后再执行。 * 2. 在真正添加到队列前检查当前要添加的请求是否在队列中,如果重复添加则无任何操作。 */
public <T> void add(int what, Request<T> request, OnResponseListener<T> responseListener) {
...
}
/** * 没有开始执行的请求数量。 */
public int unStartSize() {}
/** * 没有完成的请求数量,包括正在执行的请求。 */
public int unFinishSize() {}
/** * 停止队列,使三个线程停止,不进行轮训队列。 */
public void stop() {}
/** * 根绝sign取消所有用sign打标的请求。 */
public void cancelBySign(Object sign) {}
/** * 取消队列中所有请求。 */
public void cancelAll() {}
}
结合上文所说的,这里贴出了所有方法和最重要的实现。在构造方法中,创建了一个threadPoolSize
大小的RequestDispatcher
数组,调用start()
的时候为数组的每一个index赋值一个真正的RequestDispatcher
线程,并启动这个线程去轮训Queue
,同时在创建RequestDispatcher
的时候把我们创建队列的时候的IRestParser
穿进去了,说明真正的调用网络还是在RequestDispatcher
这个子线程中,具体怎么调用并处理接着看下文。
关于队列和线程的知识点这里不讲太多,只讲NoHttp在这里的设计,关于更多多线程、队列和任务优先级顺序的知识,请看严振杰的视频讲解。
我们上面说到RequestDispatcher
是一个线程,而且它在轮训队列里面的请求,根绝开始的流程图它也负责发送请求结果到主线程,所以它应该NoHttp异步请求中最重要的类之一了,我们不能放过它:
public class RequestDispatcher extends Thread {
// 线程轮训是否停止。
private boolean mQuit = false;
// 停止线程轮训。
public void stop() {
mQuit = true;
}
...
@Override
public void run() {
while (!mQuit) { // 线程轮训没有停止则无限循环。
final Request<?> request;
try {
// 轮训请求,如果队列中请求为空则阻塞。
request = mRequestQueue.take();
} catch (InterruptedException e) {
continue;
}
if (request.isCanceled()) {// 如果请求已经被取消则进行下一个请求。
continue;
}
// 记录下what和listener,等待结果回调。
final int what = request.what();
final OnResponseListener<?> responseListener = request.responseListener();
request.start(); // 标志请求开始。
// 发送请求开始回调到主线程。
final ThreadPoster startThread = new ThreadPoster(what, responseListener);
startThread.onStart();
PosterHandler.getInstance().post(startThread);
// 用IRestParser发送请求,并解析结果,这就是上文中说过的。
Response<?> response = mIRestParser.parserRequest(request);
// 执行完请求,从队列移除。
mUnFinishQueue.remove(request);
// 发送请求完成回调到主线程。
final ThreadPoster finishThread = new ThreadPoster(what, responseListener);
finishThread.onFinished();
PosterHandler.getInstance().post(finishThread);
request.finish();// 标志网络请求已经完成。
// 发送请求结果到主线程,如果请求在请求过程中被取消,则不发送。
if (request.isCanceled())
Logger.d(request.url() + " finish, but it's canceled.");
else {
final ThreadPoster responseThread = new ThreadPoster(what, responseListener);
responseThread.onResponse(response);
PosterHandler.getInstance().post(responseThread);
}
}
}
private class ThreadPoster implements Runnable {
public static final int COMMAND_START = 0;
public static final int COMMAND_RESPONSE = 1;
public static final int COMMAND_FINISH = 2;
private final int what;
private final OnResponseListener responseListener;
private int command;
private Response response;
public ThreadPoster(int what, OnResponseListener<?> responseListener) {
this.what = what;
this.responseListener = responseListener;
}
public void onStart() {
this.command = COMMAND_START;
}
public void onResponse(Response response) {
this.command = COMMAND_RESPONSE;
this.response = response;
}
public void onFinished() {
this.command = COMMAND_FINISH;
}
@Override
public void run() {
if (responseListener != null) {
if (command == COMMAND_START) // 开始回调。
responseListener.onStart(what);
else if (command == COMMAND_FINISH) // 结束回调。
responseListener.onFinish(what);
else if (command == COMMAND_RESPONSE) {// 请求结果回调。
if (response.isSucceed()) {// 如果成功,回调成功方法。
responseListener.onSucceed(what, response);
} else { // 如果失败,回调失败方法。
responseListener.onFailed(what, response);
}
}
}
}
}
}
如果你认真看了我的注释结合我的解释也许就会明白很多了。
RequestDispatcher
在线程没有被标志停止的情况下会一直循环调用Queue.take()
轮训队列中的请求,如果线程中没有请求,由于Queue.take()
的特性,这个子线程会处于阻塞状态,当然这不会使APP卡顿,因为它在子线程。当它每拿到一个Request
先会判断请求是否被取消,如果是取消了的则去轮训下一个请求,如果没有取消会利用Handler
发送一个Runnable
回调Listener.onStart()
方法通知主线程请求开始了,接着去执行Request
,其中最重要的一句就是调用IRestParse
的地方,这里正好印证了我们最开始讲的,在子线程中利用IRestParse
去发送请求并解析结果成泛型:
// 用IRestParser发送请求,并解析结果,这就是上文中说过的。
Response<?> response = mIRestParser.parserRequest(request);
执行完请求后再次利用Handler
发送一个Runnable
回调Listener.onFinish()
方法通知主线程网络请求执行完了:
// 发送请求完成回调到主线程。
final ThreadPoster finishThread = new ThreadPoster(what, responseListener);
finishThread.onFinished();
PosterHandler.getInstance().post(finishThread);
request.finish();
// 发送请求结果到主线程,如果请求在请求过程中被取消,则不发送。
if (request.isCanceled())
Logger.d(request.url() + " finish, but it's canceled.");
else {
final ThreadPoster responseThread = new ThreadPoster(what, responseListener);
responseThread.onResponse(response);
PosterHandler.getInstance().post(responseThread);
}
可以看到如果回调了onStart()
则一定会回调onFinish()
,所以我们在OnResponseListener
的onStart
和onFinish
非常适合做一个Dilaog
的显示和关闭。
因为请求网络可能耗时比较长或者网络不好超时等因素,用户可能会取消这个请求,所以我们看到在回调了onFinish()
后在发送结果到主线程前NoHttp又做了一个请求是否被取消的判断,综上所述我们得出一系列结论:
- 我们可以在
onStart()
时显示了一个Dialog
,那么我们可以在onFinish()
关闭Dialog
。- 如果请求开始之前就取消了,那这个请求不会被执行。
- 如果请求已经开始了,但是被中途取消,
onFinish()
还是会被回调,所以在这里关闭Dialog
是非常合适的;同时请求若是被中途取消,那么也一定不会回调onSucceed()
和onFailed()
了,这里就涉及到取消请求了,用户退出当前页面会不会发生内存泄漏的问题,答案自然是不会。
看完了RequestQueue
和RequestDispatcher
后发现,里面除了和主线程的交互外,就是和网络的交互和结果如果解析了。
由上可以看到在创建队列、启动子线程到真正的执行请求,最终都需要响应解析者IRestParser
,我们来看下它的源代码:
public interface IRestParser {
/** * 请求网络并且解析结果。 */
<T> Response<T> parserRequest(IParserRequest<T> request);
}
根据源码和流程图的结构来看,它负责针对IRestRequest
解析出数据并返回Response
,数据肯定是来自网络,因此它也怎么从网络拿到数据并解析成我们想要的泛型结果就是重点了。但它只是一个接口,想知道它是如何实现的,就得看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。
我们选中IRestParser
,键盘上按下Ctrl + T
(也许你的快捷键跟我不一样)就看到了接口的实现类,同时NoHttp为它提供的默认实现在创建队列的时候可以看到RestProtocol
public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}
我们可以看到源码是用RestParser.getInstance(iRestProtocol)
单例模式从RestParser
获取IRestParser
的实例,因此下面接着就要撸RestParser
的源码了:
public class RestParser implements IRestParser {
private static RestParser _INSTANCE;
private final IRestProtocol mIRestProtocol;
public static IRestParser getInstance(IRestProtocol implRestConnection) {
synchronized (RestParser.class) {
if (_INSTANCE == null)
_INSTANCE = new RestParser(implRestConnection);
return _INSTANCE;
}
}
private RestParser(IRestProtocol iRestProtocol) {
this.mIRestProtocol = iRestProtocol;
}
@Override
public <T> Response<T> parserRequest(IParserRequest<T> request) {
long startTime = SystemClock.elapsedRealtime(); // 记录请求开始的时间。
// 调用Http协议处理器IRestProtocol分析Request并完成网络请求拿到Response结果。
ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request);
boolean isFromCache = httpResponse.isFromCache(); // 是否来自缓存的结果。
Headers responseHeaders = httpResponse.responseHeaders(); // 服务器相应头。
Exception exception = httpResponse.exception(); // 请求时是否发生异常。
T result = null;
byte[] responseBody = httpResponse.responseBody(); // 服务器响应内容。
if (exception == null) {
try {
// 反调用IParserRequest去解析泛型结果。
result = request.parseResponse(responseHeaders, responseBody);
} catch (Throwable e) {
exception = new ParseError("Parse data error: " + e.getMessage());
}
}
return new RestResponse<T>(request, isFromCache, responseHeaders,
result, SystemClock.elapsedRealtime() - startTime, exception);
}
}
这里的单例和构造方法不多说,如果这都看不懂的话你可能不太适合做一枚程序猿,我们接着分析parserRequest()
方法。
parserRequest()
中是真正的请求网络,这里可以理解为同步请求的开始,所以看到这里不是有了灵感,NoHttp的同步请求一并看了,这也是NoHttp设计上的优点,同步请求更多的内容看本文最后。
这里要特别注意的一点是:生成IRestParser
单例时需要传入一个IRestProtocol
,而parserRequest()
开始时记录了请求开始时间,然后立刻调用传入的IRestProtocol
真正的执行网络请求:
// 调用Http协议处理器IRestProtocol分析Request并完成网络请求拿到Response结果。
ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request);
理论上讲
IRestParser
接口应该完全完成网络请求,解析出结果交给RequestDispatcher
,但是实际上IRestParser
的默认实现类RestParser
把网络请求等繁琐的操作交给IRestProtocol
接口去处理了,它只想负责拿到数据后的解析工作,这就叫做解耦。
完成网络协议请求后拿到ByteArray
后调用IParserRequest.parseResponse()
解析泛型结果,所以具体解析成什么结果是由IParserRequest
决定的(题外话:因此我们在自定义请求时,继承RestRequest需要重写parseResponse解析结果),解析完ByteArray
成泛型的结果后把异常、是否来自缓存和结果封装成RestResponse
返回,剩下的事情就是上文中分析过的RequestDispatcher
处理了,如果你忘记了RequestDispatcher
的逻辑,可以回头去看看。
我们上面说到和Http相关的的网络请求是通过IRestProtoco
这个接口发出的,so,接下来该撸IRestProtocol
这个接口了。
IRestProtocol
文章开头就说道了,它是一个接口,没啥好分析的,直接打开看源码:
public interface IRestProtocol {
/** * 解析Http协议相关参数,完成网络请求。 */
ProtocolResult requestNetwork(IProtocolRequest request);
}
根据源码和流程图的结构来看,它负责针对IProtocolRequest
解析Http协议参数,并发起网络请求返回请求的结果。因此我们需要关心的地方是它如何处理Http协议的参数,so我们接着看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。
选中IRestProtocol
,键盘上按下Ctrl + T
(也许你的快捷键跟我不一样)就看到了接口的实现类,或者回去看创建RequestQueue时,获取IRestParser
的单例时:
public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, IRestConnection connection, int threadPoolSize) {
return newRequestQueue(RestProtocol.getInstance(cache, connection), threadPoolSize);
}
public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}
public static RequestQueue newRequestQueue(IRestParser implRestParser, int threadPoolSize) {
RequestQueue requestQueue = new RequestQueue(implRestParser, threadPoolSize);
requestQueue.start();
return requestQueue;
}
这里可以看到最终创建队列时需要一个IRestParser
,而在上一个方法获取IRestParser
时需要一个IRestProtocol
接口作为参数,在最上面的方法中看到IRestProtocol
的是由RestProtocol.getInstance(cache)
生成的,所以NoHttp为IRestProtocol
提供的默认实现类是RestProtocol
。
由于这个类代码有点多,我们一点点的来分析,首先就是IRestProtocol
必须要实现的方法requestNetwork()
:
public class RestProtocol implements IRestProtocol {
@Override
public ProtocolResult requestNetwork(IProtocolRequest request) {
// 处理Http缓存头。
CacheMode cacheMode = request.getCacheMode();
String cacheKey = request.getCacheKey();
CacheEntity cEntity = mCache.get(cacheKey);
ProtocolResult result;
// 根据缓存模式处理。
switch (cacheMode) {
case ONLY_READ_CACHE:// 只读缓存.
if (cEntity == null) {
return new ProtocolResult(null, null, true, new NotFoundCacheError("没找到缓存"));
} else {
return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
}
case ONLY_REQUEST_NETWORK:// 仅仅请求网络.
result = getHttpResponse(request);
break;
case NONE_CACHE_REQUEST_NETWORK:// 先读缓存,没缓存再请求网络。
if (cEntity == null) {
result = getHttpResponse(request);// 请求网络。
} else {
return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
}
break;
case REQUEST_NETWORK_FAILED_READ_CACHE:// 请求网络失败后读缓存。
if (cEntity != null)
setRequestCacheHeader(request, cEntity); // 缓存存在时设置缓存头。
result = getHttpResponse(request);// 请求网络。
break;
default:// 按照Http标准协议,304。
if (cEntity != null) {
// 缓存没失效直接返回。
if (cEntity.getLocalExpire() > System.currentTimeMillis()) {
return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
}
// 缓存失败但存在,时设置缓存头。
setRequestCacheHeader(request, cEntity);
}
result = getHttpResponse(request);// 请求网络。
break;
}
return handleResponseCache(request, cEntity, result); // 处理响应数据,缓存协议等。
}
要注意的两点:
第一:NoHttp的几种缓存模式:
第二:此方法中出现的几个未知的方法:
这个方法是在请求之前为Request添加和缓存协议有关的请求头,这是Http标准协议的内容,更多的协议讲解请看这篇博客:http://blog.csdn.net/yanzhenjie1003/article/details/50878323
private void setRequestCacheHeader(IProtocolRequest request, CacheEntity cacheEntity) {
if (cacheEntity == null) { // 如果缓存为空,移除缓存相关请求头。
request.headers().remove("If-None-Match");
request.headers().remove("If-Modified-Since");
} else { // 缓存不为空则添加相关请求头。
Headers headers = cacheEntity.getResponseHeaders();
String eTag = headers.getETag();
if (eTag != null) {
request.headers().set("If-None-Match", eTag);
}
long lastModified = headers.getLastModified();
if (lastModified > 0) {
request.headers().set("If-Modified-Since", HeaderUtil.formatMillisToGMT(lastModified));
}
}
}
这里需要解释一下Http的缓存协议了。如果服务器支持http标准的缓存,当我们第一次请求服务器的时候,服务器会在响应头中添加Last-Modified
头,这个头的含义是服务器最后一次修改响应body
的时间,如果服务器支持设置缓存有效期,还会添加一个E-Tag
的头,这个头是可以理解为响应body
的一个tag
。客户端接受到响应头到,会把这两个相应头保存起来,在第二次请求的时候会首先检查响应body
是否过期,如果没有过期则直接使用上次的响应body
,也就是我们在requestNetwork()
方法中看到的:
default:// 按照Http标准协议,304。
if (cEntity != null) {
// 缓存没失效直接返回。
if (cEntity.getLocalExpire() > System.currentTimeMillis()) {
return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
}
// 缓存失败但存在,时设置缓存头。
setRequestCacheHeader(request, cEntity);
}
result = getHttpResponse(request);// 请求网络。
break;
如果响应body
过期,那么客户端应该重新请求服务器,并且在请求头中添加两个请求头,第一个是If-None-Match
头,这个头的值应该是上次请求服务器时,服务器返回的E-Tag
,第二个要添加的请求头是If-Modified-Since
,这个头的值应该是上次服务器返回的Last-Modified
的值。
服务器接受到请求后会用If-None-Match
和If-Modified-Since
和服务器现在E-Tag
和Last-Modified
做对比,判断客户端上次的缓存是否过期。如果没有过期则返回304响应码,并且不会向响应body
中写入内容,客户端接受到这个响应后,判断响应码是304时应该从客户端拿上次的缓存数据;如果过期则返回200段的响应码,并且会向响应body
中写入新的内容,客户端接受到这个响应后,判断响应码是200段,应该重新从响应body
中读取内容。
这就是Http标准协议中的缓存协议内容,NoHttp做到了完美的支持。
/** * 真正的请求网络。 */
private ProtocolResult getHttpResponse(IProtocolRequest request) {
byte[] responseBody = null;
Connection connection = iRestConnection.getConnection(request); // 从IRestConnection拿到网络连接和响应内容。
Headers responseHeaders = connection.responseHeaders();
Exception exception = connection.exception();
if (exception == null) {
// 判断是否有响应内容,比如304响应码就没有body。
if (hasResponseBody(request.getRequestMethod(), responseHeaders.getResponseCode()))
try {
// 把服务器响应body转为ByteArray。
responseBody = IOUtils.toByteArray(connection.serverStream());
} catch (IOException e) {// IOException.
exception = e;
}
}
IOUtils.closeQuietly(connection); // 关闭服务器流。
// 返回响应内容。
return new ProtocolResult(responseHeaders, responseBody, exception != null, exception);
}
其中Connection connection = iRestConnection.getConnection(request); 。
里面才是真正的建立网络请求,发送数据、上传文件等操作。
private ProtocolResult handleResponseCache(IProtocolRequest request, CacheEntity localCacheEntity, ProtocolResult httpResponse) {
boolean isFromCache = false;
Headers responseHeaders = httpResponse.responseHeaders();
byte[] responseBody = httpResponse.responseBody();
Exception exception = httpResponse.exception();
CacheMode cacheMode = request.getCacheMode();
int responseCode = responseHeaders.getResponseCode();
if (exception == null) {// 没有发生异常,请求成功。
if (responseCode == 304) {
isFromCache = true;
if (localCacheEntity == null) { // 服务器304,但本地没有缓存,兼容服务器bug。
responseBody = new byte[0];
} else {
// 更新相应头信息。
localCacheEntity.getResponseHeaders().setAll(responseHeaders);
responseHeaders = localCacheEntity.getResponseHeaders();
localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders));
// 读取本地缓存数据。
responseBody = localCacheEntity.getData();
}
} else if (responseBody != null) {// 响应码非304,并且有响应内容。
if (localCacheEntity == null) { // 如果本地没缓存,则根据http协议进行缓存。
// 解析服务器缓存头。
localCacheEntity = HeaderUtil.parseCacheHeaders(...);
// 这里解析出来也许为空,因为服务器可能不允许缓存:no-cache,no-store。
} else {
// 如果本地已经有缓存,则更新数据。
localCacheEntity.getResponseHeaders().setAll(responseHeaders);
localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders));
localCacheEntity.setData(responseBody);
}
}
if (localCacheEntity != null) {
// 解析响应头后服务区允许缓存或者已经有缓存,更新数据库缓存数据。
mCache.replace(request.getCacheKey(), localCacheEntity);
}
} else if (cacheMode == CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE
&& localCacheEntity != null) {
// 如果请求失败,但是缓存模式是请求失败后读缓存,那么读缓存数据。
exception = null;
isFromCache = true;
responseHeaders = localCacheEntity.getResponseHeaders();
responseBody = localCacheEntity.getData();
}
return new ProtocolResult(responseHeaders, responseBody, isFromCache, exception);
}
这个类的作用是根据服务器响应码、服务器响应头、本地数据等处理和缓存相关的逻辑,大家可以认真看下源码和逻辑。主要做的事情是,解析服务器响应码、响应内容,根据标准的http协议来决定是否缓存数据,或者更新上次的缓存数据。
同样的理论上讲
IRestProtocol
接口应该完全完成网络请求,解析出结果交给IRestParser
,但是实际上IRestProtocol
的默认实现类RestProtocol
把网络请求发送数据等繁琐的操作交给IRestConnection
接口去处理了,它只负责处理与Http相关的协议等,这也叫做解耦。
看到这里的读者朋友现在最关心的应该是:Connection connection = iRestConnection.getConnection(request);
中IRestConnection
接口如何实现网络请求之类的,我们上面也提到了替换NoHttp的网络底层为OkHttp时需要只实现IRestConnection
,那么下面我们就对IRestConnection
一探究竟。
为什么说替换NoHttp底层库为OkHttp只需要实现一个简单的接口呢?其实细心的朋友已经看到了,我们在创建队列时需要一个IRestConnection
:
public static RequestQueue newRequestQueue(int threadPoolSize) {
// 调用下一个方法:这里传入RestConnection.getInstance()生成IRestConnection单例。
return newRequestQueue(DiskCacheStore.INSTANCE, RestConnection.getInstance(), threadPoolSize);
}
// 这里需要一个IRestConnection参数。
public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, IRestConnection connection, int threadPoolSize) {
return newRequestQueue(RestProtocol.getInstance(cache, connection), threadPoolSize);
}
public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}
这里很明了,我们先看IRestConnection
的源码:
public interface IRestConnection {
/** * 拿到网络连接的各种属性、头、流。 */
Connection getConnection(IBasicRequest iBasicRequest);
}
看到这里,有一个返回参数Connection
,我们要想用OkHttp来实现这个方法,必须要看这个类是什么样的:
public interface Connection extends Closeable {
/** * 拿到URL对象。 */
URL getURL();
/** * 拿到相应头。 */
Headers responseHeaders();
/** * 拿到服务器的输出流。 */
InputStream serverStream();
/** * 拿到请求过程中的异常。 */
Exception exception();
}
这个返回参数类是一个接口,也就说明了我们可以自定义这个类,NoHttp本身提供的类我们暂且不管,这里其实我们按照下面四个方法返回值即可:一URL、一个拿服务器的响应头、一个是服务器的输出流,一个是请求过程中是否发生异常。这样就非常好替换OkHttp了,我们底层用OkHttp请求网络,然后同样给IRestConnection
返回这个对象,里面把服务器的相应头、输出流、请求过程中的异常塞进去就OK了。
更多细节请看替换NoHttp底层为OkHttp的教程:
http://blog.csdn.net/yanzhenjie1003/article/details/52439317。
今天我们的请求模块到这里就解析完了,看完文章的同学也许还会有不明白的地方,欢迎大家在博客下方留言。
还剩下BasicConnection
,因为它的能力请求网络、发送数据、上传文件、拿到服务器响应头、拿到服务器响应body、拿到服务器的流等内容较多,我会专门开一篇新的博客专门讲这个类。
RequestQueue
是请求队列,在主线程执行,主线程可以添加Request
到请求队列中,等待子线程轮训读取Request
去执行。
RequestDispatcher
是请求分发者,它是一个子线程,作用是轮训请求队列,拿到请求后调用IRestParser
执行请求结果,负责发送开始请求、结束请求、把结果发送到主线程等重要任务。
2.1 RequestQueue
、RequestDispatcher
可以接受的请求类是Request
接口,Request
接口继承自IParserRequest
接口,Request
接口负责记录响应监听器Listener和监听器的what对象。
IRestParser
是请求结果解析者,它在子线程中运行,负责把从网络请求回来的ByteArray解析成开发者想要的泛型对象。
3.1 IRestParser
接口带有泛型,可以接受到的请求类是IParserRequest
接口,IParserRequest
接口继承自IRestPtotocol
接口,IParserRequest
接口负责具体实现解析成开发者想要的泛型对象,由IRestParser
具体调用。
IRestProtocol
是协议处理器,它在子线程中运行,负责从网络请求结果,包括Header、Body(ByteArray),并且在请求到结果前后处理缓存协议(其他协议待加)等。
4.1 IRestProtocol
接口可以接受的请求类是IProtocolRequest
接口,IProtocolRequest
接口继承自IBasicRequest
接口,IProtocolRequest
接口负责记录客户端自定义Http协议等,例如缓存模式,缓存Key等。
IRestConnection
是网络访问接口,它的能力:能力请求网络、发送数据、上传文件、拿到服务器响应头、拿到服务器响应body、拿到服务器的流等,是下载模块和请求模块的通用网络请求模块。
5.1 IRestConnection
接口的请求类是IBasicRequest
接口,IBasicRequest
接口记录了基础的请求属性、通用的网络设置、参数设置、Cookie、重试机制,下载和请求模块通用。
版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003