Android NoHttp源码阅读指导
2017-02-07 20:25
225 查看
http://blog.csdn.net/yanzhenjie1003/article/details/52413226
版权声明:转载必须注明本文转自严振杰的博客: 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的异步,我们先来看一个最简单实践:
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
[/code]
来做个简单的分析,首先是new了一个队列,然后创建了一个String的Request,之后把这个请求添加到队列,等待请求网络后响应,完事儿,就是这么简单。
NoHttp同步请求,再来看一个最简单实例:
2
1
2
[/code]
这个更简单了,创建了一个请求,然后调用NoHttp同步请求的方法拿到请求结果。
从上面的代码中看到,异步请求和同步请求最大的区别是:异步请求需要用队列发送,用
这个图是NoHttp异步请求时,从主线程调用网络到拿到结果的流程图,这里非常有必要对这些类做个说明。
注意:其中
从上面的两张图可以看到,任何一个请求都是从主线程开始发起,先把Request添加到
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[/code]
这里有5个方法可以创建
那么
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
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
[/code]
结合上文所说的,这里贴出了所有方法和最重要的实现。在构造方法中,创建了一个
关于队列和线程的知识点这里不讲太多,只讲NoHttp在这里的设计,关于更多多线程、队列和任务优先级顺序的知识,请看严振杰的视频讲解。
我们上面说到
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
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
[/code]
如果你认真看了我的注释结合我的解释也许就会明白很多了。
2
1
2
[/code]
执行完请求后再次利用
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
可以看到如果回调了
因为请求网络可能耗时比较长或者网络不好超时等因素,用户可能会取消这个请求,所以我们看到在回调了
我们可以在
如果请求开始之前就取消了,那这个请求不会被执行。
如果请求已经开始了,但是被中途取消,
看完了
由上可以看到在创建队列、启动子线程到真正的执行请求,最终都需要响应解析者
2
3
4
5
6
1
2
3
4
5
6
[/code]
根据源码和流程图的结构来看,它负责针对
我们选中
2
3
1
2
3
[/code]
我们可以看到源码是用
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
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
[/code]
这里的单例和构造方法不多说,如果这都看不懂的话你可能不太适合做一枚程序猿,我们接着分析
这里要特别注意的一点是:生成
2
1
2
[/code]
理论上讲
完成网络协议请求后拿到
我们上面说到和Http相关的的网络请求是通过
2
3
4
5
6
1
2
3
4
5
6
[/code]
根据源码和流程图的结构来看,它负责针对
选中
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]
这里可以看到最终创建队列时需要一个
由于这个类代码有点多,我们一点点的来分析,首先就是
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
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
[/code]
要注意的两点:
第一:NoHttp的几种缓存模式:
只读缓存
只请求缓存
先读缓存,没有缓存再请求网络
先请求网络,请求网络失败再读取缓存
按照Http标准协议,重定向缓存机制
第二:此方法中出现的几个未知的方法:
setRequestCacheHeader(request, cacheEntity); // 为请求设置缓存头。
getHttpResponse(request); // 真正的请求网络。
handleResponseCache(request, cEntity, result); // 处理响应结果、缓存等。
这个方法是在请求之前为Request添加和缓存协议有关的请求头,这是Http标准协议的内容,更多的协议讲解请看这篇博客:http://blog.csdn.net/yanzhenjie1003/article/details/50878323
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]
这里需要解释一下Http的缓存协议了。如果服务器支持http标准的缓存,当我们第一次请求服务器的时候,服务器会在响应头中添加
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]
如果
服务器接受到请求后会用
这就是Http标准协议中的缓存协议内容,NoHttp做到了完美的支持。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]
其中
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
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
[/code]
这个类的作用是根据服务器响应码、服务器响应头、本地数据等处理和缓存相关的逻辑,大家可以认真看下源码和逻辑。主要做的事情是,解析服务器响应码、响应内容,根据标准的http协议来决定是否缓存数据,或者更新上次的缓存数据。
看到这里的读者朋友现在最关心的应该是:
为什么说替换NoHttp底层库为OkHttp只需要实现一个简单的接口呢?其实细心的朋友已经看到了,我们在创建队列时需要一个
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]
这里很明了,我们先看
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]
看到这里,有一个返回参数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[/code]
这个返回参数类是一个接口,也就说明了我们可以自定义这个类,NoHttp本身提供的类我们暂且不管,这里其实我们按照下面四个方法返回值即可:一URL、一个拿服务器的响应头、一个是服务器的输出流,一个是请求过程中是否发生异常。这样就非常好替换OkHttp了,我们底层用OkHttp请求网络,然后同样给
更多细节请看替换NoHttp底层为OkHttp的教程:
http://blog.csdn.net/yanzhenjie1003/article/details/52439317。
还剩下
2.1
3.1
4.1
5.1
版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003
顶7
踩1
Android NoHttp源码阅读指导
版权声明:转载必须注明本文转自严振杰的博客: 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的异步请求和同步请求的简单用法。切入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){} });1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
[/code]
来做个简单的分析,首先是new了一个队列,然后创建了一个String的Request,之后把这个请求添加到队列,等待请求网络后响应,完事儿,就是这么简单。
NoHttp同步请求,再来看一个最简单实例:
Request<String> request = new StringRequest(url, GET); Response<String> response = NoHttp.startRequestSync(request);1
2
1
2
[/code]
这个更简单了,创建了一个请求,然后调用NoHttp同步请求的方法拿到请求结果。
从哪里开始看起
看了异步请求和同步请求后我们来分析一下,应该从哪里开始看NoHttp的源码。从上面的代码中看到,异步请求和同步请求最大的区别是:异步请求需要用队列发送,用
Listener接受请求结果,而同步请求是直接请求到结果,那么最大的可能就是异步请求是建立在同步请求这个基础上的。所以我们直接看异步请求,在开始之前我已经画好了几个图。
NoHttp流程图
这个图是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了。
NoHttp动态流程图
如果上面的图还是不够显然,我们可以结合下面的图再看一边动态流程图就更加显而易懂了:从上面的两张图可以看到,任何一个请求都是从主线程开始发起,先把Request添加到
RequestQueue(队列)中,队列此时会启动子线程,再由
ReuqestDispatcher(请求分发者)把队列中请求按照一定的顺序分发给子线程去执行网络操作,
ReuqestDispatcher拿到结果后,用
Handler发送到主线程,这个过程就是NoHttp的整个异步请求的过程。
撸源码
上面是大致的逻辑说明,接着上面的分析,我们来个总分总,现在我们现在开始撸源码,把各个环节的细节给拆开了,那么先入手的入手的就是RequestQueue了。
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; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[/code]
这里有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() {} }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
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
[/code]
结合上文所说的,这里贴出了所有方法和最重要的实现。在构造方法中,创建了一个
threadPoolSize大小的
RequestDispatcher数组,调用
start()的时候为数组的每一个index赋值一个真正的
RequestDispatcher线程,并启动这个线程去轮训
Queue,同时在创建
RequestDispatcher的时候把我们创建队列的时候的
IRestParser穿进去了,说明真正的调用网络还是在
RequestDispatcher这个子线程中,具体怎么调用并处理接着看下文。
关于队列和线程的知识点这里不讲太多,只讲NoHttp在这里的设计,关于更多多线程、队列和任务优先级顺序的知识,请看严振杰的视频讲解。
RequestDispatcher
我们上面说到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); } } } } } }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
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
[/code]
如果你认真看了我的注释结合我的解释也许就会明白很多了。
RequestDispatcher在线程没有被标志停止的情况下会一直循环调用
Queue.take()轮训队列中的请求,如果线程中没有请求,由于
Queue.take()的特性,这个子线程会处于阻塞状态,当然这不会使APP卡顿,因为它在子线程。当它每拿到一个
Request先会判断请求是否被取消,如果是取消了的则去轮训下一个请求,如果没有取消会利用
Handler发送一个
Runnable回调
Listener.onStart()方法通知主线程请求开始了,接着去执行
Request,其中最重要的一句就是调用
IRestParse的地方,这里正好印证了我们最开始讲的,在子线程中利用
IRestParse去发送请求并解析结果成泛型:
// 用IRestParser发送请求,并解析结果,这就是上文中说过的。 Response<?> response = mIRestParser.parserRequest(request);1
2
1
2
[/code]
执行完请求后再次利用
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); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
可以看到如果回调了
onStart()则一定会回调
onFinish(),所以我们在
OnResponseListener的
onStart和
onFinish非常适合做一个
Dilaog的显示和关闭。
因为请求网络可能耗时比较长或者网络不好超时等因素,用户可能会取消这个请求,所以我们看到在回调了
onFinish()后在发送结果到主线程前NoHttp又做了一个请求是否被取消的判断,综上所述我们得出一系列结论:
我们可以在
onStart()时显示了一个
Dialog,那么我们可以在
onFinish()关闭
Dialog。
如果请求开始之前就取消了,那这个请求不会被执行。
如果请求已经开始了,但是被中途取消,
onFinish()还是会被回调,所以在这里关闭
Dialog是非常合适的;同时请求若是被中途取消,那么也一定不会回调
onSucceed()和
onFailed()了,这里就涉及到取消请求了,用户退出当前页面会不会发生内存泄漏的问题,答案自然是不会。
IRestParser和它的实现类RestParser
看完了RequestQueue和
RequestDispatcher后发现,里面除了和主线程的交互外,就是和网络的交互和结果如果解析了。
由上可以看到在创建队列、启动子线程到真正的执行请求,最终都需要响应解析者
IRestParser,我们来看下它的源代码:
public interface IRestParser { /** * 请求网络并且解析结果。 */ <T> Response<T> parserRequest(IParserRequest<T> request); }1
2
3
4
5
6
1
2
3
4
5
6
[/code]
根据源码和流程图的结构来看,它负责针对
IRestRequest解析出数据并返回
Response,数据肯定是来自网络,因此它也怎么从网络拿到数据并解析成我们想要的泛型结果就是重点了。但它只是一个接口,想知道它是如何实现的,就得看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。
我们选中
IRestParser,键盘上按下
Ctrl + T(也许你的快捷键跟我不一样)就看到了接口的实现类,同时NoHttp为它提供的默认实现在创建队列的时候可以看到
RestProtocol
public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) { return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize); }1
2
3
1
2
3
[/code]
我们可以看到源码是用
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); } }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
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
[/code]
这里的单例和构造方法不多说,如果这都看不懂的话你可能不太适合做一枚程序猿,我们接着分析
parserRequest()方法。
parserRequest()中是真正的请求网络,这里可以理解为同步请求的开始,所以看到这里不是有了灵感,NoHttp的同步请求一并看了,这也是NoHttp设计上的优点,同步请求更多的内容看本文最后。
这里要特别注意的一点是:生成
IRestParser单例时需要传入一个
IRestProtocol,而
parserRequest()开始时记录了请求开始时间,然后立刻调用传入的
IRestProtocol真正的执行网络请求:
// 调用Http协议处理器IRestProtocol分析Request并完成网络请求拿到Response结果。 ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request);1
2
1
2
[/code]
理论上讲
IRestParser接口应该完全完成网络请求,解析出结果交给
RequestDispatcher,但是实际上
IRestParser的默认实现类
RestParser把网络请求等繁琐的操作交给
IRestProtocol接口去处理了,它只想负责拿到数据后的解析工作,这就叫做解耦。
完成网络协议请求后拿到
ByteArray后调用
IParserRequest.parseResponse()解析泛型结果,所以具体解析成什么结果是由
IParserRequest决定的(题外话:因此我们在自定义请求时,继承RestRequest需要重写parseResponse解析结果),解析完
ByteArray成泛型的结果后把异常、是否来自缓存和结果封装成
RestResponse返回,剩下的事情就是上文中分析过的
RequestDispatcher处理了,如果你忘记了
RequestDispatcher的逻辑,可以回头去看看。
我们上面说到和Http相关的的网络请求是通过
IRestProtoco这个接口发出的,so,接下来该撸
IRestProtocol这个接口了。
IRestProtocol和它的实现类RestProtocol
IRestProtocol文章开头就说道了,它是一个接口,没啥好分析的,直接打开看源码:
public interface IRestProtocol { /** * 解析Http协议相关参数,完成网络请求。 */ ProtocolResult requestNetwork(IProtocolRequest request); }1
2
3
4
5
6
1
2
3
4
5
6
[/code]
根据源码和流程图的结构来看,它负责针对
IProtocolRequest解析Http协议参数,并发起网络请求返回请求的结果。因此我们需要关心的地方是它如何处理Http协议的参数,so我们接着看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。
选中
IRestProtocol,键盘上按下
Ctrl + T(也许你的快捷键跟我不一样)就看到了接口的实现类,或者回去看创建RequestQueue时,获取
IRestParser的单例时:
public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, IRestConnection connection, int threadPoolSize) {1
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]
这里可以看到最终创建队列时需要一个
IRestParser,而在上一个方法获取
IRestParser时需要一个
IRestProtocol接口作为参数,在最上面的方法中看到
IRestProtocol的是由
RestProtocol.getInstance(cache)生成的,所以NoHttp为
IRestProtocol提供的默认实现类是
RestProtocol。
ProtocolResult requestNetwork(IProtocolRequest request);
由于这个类代码有点多,我们一点点的来分析,首先就是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); // 处理响应数据,缓存协议等。 }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
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
[/code]
要注意的两点:
第一:NoHttp的几种缓存模式:
只读缓存
只请求缓存
先读缓存,没有缓存再请求网络
先请求网络,请求网络失败再读取缓存
按照Http标准协议,重定向缓存机制
第二:此方法中出现的几个未知的方法:
setRequestCacheHeader(request, cacheEntity); // 为请求设置缓存头。
getHttpResponse(request); // 真正的请求网络。
handleResponseCache(request, cEntity, result); // 处理响应结果、缓存等。
setRequestCacheHeader(request, cacheEntity);
这个方法是在请求之前为Request添加和缓存协议有关的请求头,这是Http标准协议的内容,更多的协议讲解请看这篇博客:http://blog.csdn.net/yanzhenjie1003/article/details/50878323private 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)); } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]
这里需要解释一下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;1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]
如果
响应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做到了完美的支持。
getHttpResponse(request);
/** * 真正的请求网络。 */ 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); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]
其中
Connection connection = iRestConnection.getConnection(request); 。里面才是真正的建立网络请求,发送数据、上传文件等操作。
handleResponseCache(request, cEntity, result);
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); }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
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
[/code]
这个类的作用是根据服务器响应码、服务器响应头、本地数据等处理和缓存相关的逻辑,大家可以认真看下源码和逻辑。主要做的事情是,解析服务器响应码、响应内容,根据标准的http协议来决定是否缓存数据,或者更新上次的缓存数据。
RestProtocol小结
同样的理论上讲IRestProtocol接口应该完全完成网络请求,解析出结果交给
IRestParser,但是实际上
IRestProtocol的默认实现类
RestProtocol把网络请求发送数据等繁琐的操作交给
IRestConnection接口去处理了,它只负责处理与Http相关的协议等,这也叫做解耦。
看到这里的读者朋友现在最关心的应该是:
Connection connection = iRestConnection.getConnection(request);中
IRestConnection接口如何实现网络请求之类的,我们上面也提到了替换NoHttp的网络底层为OkHttp时需要只实现
IRestConnection,那么下面我们就对
IRestConnection一探究竟。
IRestConnection网络层:如何替换网络层为OkHttp
为什么说替换NoHttp底层库为OkHttp只需要实现一个简单的接口呢?其实细心的朋友已经看到了,我们在创建队列时需要一个IRestConnection:
public static RequestQueue newRequestQueue(int threadPoolSize) {1
// 调用下一个方法:这里传入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); }
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]
这里很明了,我们先看
IRestConnection的源码:
public interface IRestConnection { /** * 拿到网络连接的各种属性、头、流。 */ Connection getConnection(IBasicRequest iBasicRequest); }1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]
看到这里,有一个返回参数
Connection,我们要想用OkHttp来实现这个方法,必须要看这个类是什么样的:
public interface Connection extends Closeable { /** * 拿到URL对象。 */ URL getURL(); /** * 拿到相应头。 */ Headers responseHeaders(); /** * 拿到服务器的输出流。 */ InputStream serverStream(); /** * 拿到请求过程中的异常。 */ Exception exception(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[/code]
这个返回参数类是一个接口,也就说明了我们可以自定义这个类,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
顶7
踩1
相关文章推荐
- 卷积神经网络——反向传播算法
- 卷积神经网络简介
- BP神经网络(完整的理论和经验公式)
- Vue.js学习系列三——axios和网络传输相关知识的学习实践
- OkHttp 3 的 Javadoc 翻译
- Python 网络抓取和文本挖掘-2 XML 和 JSON
- TensorFlow 深度学习笔记 从线性分类器到深度神经网络
- 使用python写神经网络模型之分类器
- 机器学习入门——初步认知人工神经网络
- 系统间通信:通过自定义协议通信加深HTTP理解
- 半数网站已用上HTTPS 你用上了吗(附转换教程)
- 解读神经网络十大误解,再也不会弄错它的工作原理
- 网络流isap
- 【java规则引擎】简单规则的rete网络示意图
- 第二章 ZeroMQ进阶
- Linux网络命令
- 用Let's Encrypt实现Https(Windows环境+Tomcat+Java)
- Android端和服务端Tomcat的https添加记录
- 网络请求框架对比
- Java网络编程下之Socket客户端编程