Okhttp的使用技巧与拦截器的使用分析

Okhttp结构框架

verson:3.4.1

Okhttp的结构从Call这个接口就可以大概知道了总的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* A call is a request that has been prepared for execution. A call can be canceled. As this object
* represents a single request/response pair (stream), it cannot be executed twice.
*
* 代表了一个单独的请求,可以被取消,不能被执行2次。
*
*/
public interface Call extends Cloneable {

Request request()
Response execute() throws IOException
void enqueue(Callback responseCallback)
void cancel()
boolean isExecuted()
boolean isCanceled()

interface Factory {
Call newCall(Request request);
}
}

Call内部有个工厂接口,只有一个newCall()方法,由OkhttpClient实现,Call的真正实现是RealCall。

Okhttp的整个调用过程:创建一个OkhttpClient对象,调用newCall方法传入一个request对象产生一个Call对象,调用call的execute或者enqueue方法获取Response对象。

同步Get请求

1
2
3
4
5
6
7
8
String runSync(String url) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}

异步Get请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void runAsync(String url){
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggerInterceptor())
.addNetworkInterceptor(new MyNetworkInterceptor())
.build();
Request request = new Request.Builder()
.url(url)
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.toString());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "onResponse: " + response.body().string());
}
});
}

Okhttp大量使用builder设计模式

Request类

每一个http请求包含URL, request method(GET/POST等等),headers, requestBody.
Request通过Request.Builder对象设置各种属性,最终调用build()生成Request对象。

Response类

Response同样是Builder结构
包含了response code(200, 401等等), headers ,response body。
(The response answers the request with a code (like 200 for success or 404 for not found), headers, and its own optional body.)

OkHttpClient类

OkHttpClient也是Builder结构,可以是设置connectTimout, readTimeout, writeTimout的时间,
设置Dispatcher, Dns, Cache,Interceptors, NetworkInterceptors等等

Interceptor (拦截器:阅读了okhttp源码,个人觉得很奇妙的设计)

上图的Interceptors里传递的Request, Response可以在每一个Interceptor进行预处理,替换等。
从而可以自定义Interceptor实现Rewriting Requests(重写Request), Rewriting Responses(重写Response), Retrying Requests(重试Request), Follow-up Requests等。

添加自定义Interceptor通过OkHttpClient配置的:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new MyInterceptor())
.addNetworkInterceptor(new MyNetworkInterceptor())
.build();

Interceptor是一个接口

1
2
3
4
5
6
7
8
9
10
11
public interface Interceptor {
Response intercept(Chain chain) throws IOException;

interface Chain {
Request request();

Response proceed(Request request) throws IOException;

Connection connection();
}
}

发现okhttp是通过执行一个List来完成整个网络请求的过程,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// RealCall里的getResponseWithInterceptorChain()方法,
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));

Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}

从上面的代码可以看出List里Interceptor的顺序,
client.interceptors() (自定义的拦截器, 在call api的前的拦截) - > retryAndFollowUpInterceptor (实现请求重试)

  • BridgeInterceptor - > CacheInterceptor - > ConnectInterceptor(真正网络连接的实现) - > client.networkInterceptors() (自定义拦截器,请求完全的拦截)

  • CallServerInterceptor

Request在Interceptor里是怎么传递的?把interceptors传递到RealInterceptorChain里,执行了chain.proceed(originalRequest).

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
//RealInterceptorChain的proceed方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !sameConnection(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}

// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}

return response;
}

proceed方法里这段代码由几处的this.httpCodec != null这样的判断,httpCodec什么时候不为null? 当ConnectInterceptor执行完后就不为null了,
也就是请求已经执行了才不为null。这里对在ConnectInterceptor之后的拦截器进行了检查,request的url要一致,interceptor必须执行一次proceed()方法,否则会抛异常。
所有Interceptor的intercept()返回的Response必须不能为null。

在RealCall里的getResponseWithInterceptorChain方法里,创建了一个RealInterceptorChain对象,调用proceed(),
在proceed方法里又创建了RealInterceptorChain对象,interceptors.get(index)获取interceptor,接着调用interceptor.intercept(next)把realInterceptorChain传递下,
在interceptor的intercept()方法里又调用proceed(),明显形成了一个递归。
通过RealInterceptorChain的构造器,传递interceptors,streamAllocation,httpCodec,connection,index, request。而index每次new一个RealInterceptorChain都会加1,
是为了保证interceptors遍历。

1
2
RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, Connection connection, int index, Request request)

这样的设计可以在Interceptor的intercept()方法里调用proceed(request,streamAllocation,httpCodec, connection)
这个方法之前替换request等参数,也可以在返回Response前替换response。灵活性就大大增加。

在ConnectInterceptor之后的拦截器必须满足:request的url要一致,interceptor必须执行一次proceed()。这样子做是为了保证递推的正常运作。
而对与client.interceptors是在ConnectInterceptor之前的拦截器,可以不用必须执行一次proceed()。可以实现直接返回虚拟的response用于是测试等功能。

参考: https://github.com/square/okhttp/wiki
Loading comments box needs to over the wall