Android网络框架OkHttp之get请求(源码初识)

2022-11-24,,,,

概括

OkHttp现在很火呀。于是上个星期就一直在学习OkHttp框架,虽然说起来已经有点晚上手了,貌似是2013年就推出了。但是现在它版本更加稳定了呀。这不,说着说着,OkHttp3.3版本在这几天又发布了。以下以OkHttp3.2版本为准,没办法,上个星期看的时候还是以3.2为最新版本的。首先,我们要先了解一些背景,OkHttp这个框架是有Square公司推出的,进入官网。如果想看API,点击进入API。大概了解了OkHttp之后,我们应该知道OkHttp是一个网络框架,想想以前在开发中,网络框架一般用的是什么?很快我们就会想到刚学习Android开发的时候接触的HttpURLConnection和Apache提供的HttpClient这两个类,然后就是后面推出的一些第三方网络框架,比如2013年google推出的Volley框架、android-async-http框架、2014年很火的Xutils、以及现在很多人用的Retrofit等等。这么多,到底选哪个?一开始我也晕。后来看了一些资料,似乎懂了一个概念:OkHttp是用来替换HttpURLConnection的,据说android4.4源码的HttpURLConnection就替换成了OkHttp。所以我们别拿OkHttp和这些网络框架比,这些网络框架也只是基于HttpURLConnection进行一些封装,使我们的代码更加简洁方便。懂了这点,我们应该就懂了为什么网上那么多OkHttp和Volley或者Retrofit等等这些框架结合使用了,其实是一个道理。那么我用的HttpUrlConnection或者HttpClient用的好好的,干嘛要用你的OkHttp?这里就来比较下HttpURLConnection和OkHttp。至于HttpClient嘛,android6.0已经把它的API废除了。用它还要引入org.apache.http.legacy.jar包,不值得,而且okhttp也已经提供了对应的okhttp-apache 模块。

HttpURLConnection和OkHttp的比较

HttpURLConnection有的API,OkHttp基本上都有(你有我有全都有呀,哈哈哈)
HttpURLConnection和OkHttp都支持Https,流的上传和下载,超时,IP6、连接池等等

OkHttp比HttpURLConnection具有更好的同步异步请求、缓存机制,支持HttpDNS、重定向、Gzip压缩,平台适应性、很好的服务器IP的转换、直接Socket通信,支持拦截器等等。

看到这么多机制,是不是觉得很强大,通过Socket直接通信,以及很好的缓存机制,Gzip对于Http头部的压缩传输。自然对于网络请求这块使应用更加省流量、请求的更快。OkHttp对于Https和HttpDNS的支持,使得应用的网络通信安全性更高。当然说了它的好,现在也来说说它的 
不好之处

OkHttp不支持优先级请求
OkHttp不支持自签名证书
OkHttp header中不能传中文

虽然是不好的地方,但是OkHttp已经比较成熟了,网上解决这几个问题的资料也很多了。所以这些都不是问题。

一个简单的Get请求例子

这里我们就以经典的官网提供的Get请求的例子来学习下,说大概的代码。

先在manifest加个网络权限,养成良好习惯

 <uses-permission android:name="android.permission.INTERNET"/>

1
1

然后在build.gradle文件的dependencies添加库如下:

dependencies {
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
}

1
2
3
4
1
2
3
4

同步Get请求:

      final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.build();
final Request request = new Request.Builder()
.url("https://www.publicobject.com/helloworld.txt")
.header("User-Agent","OkHttp Example")
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = okHttpClient.newCall(request).execute();
Log.d("zgx","response====="+response.body().string());
response.body().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这个例子网上说烂了,没啥说的,来看下结果 

很漂亮的样子。呵呵

异步Get请求: 
修改上面部分代码,Call类调用enqueue方法。代码如下:

  new Thread(new Runnable() {
@Override
public void run() {
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("zgx","response====="+e.getMessage());
} @Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("zgx","response====="+response.body().string());
response.body().close();
}
});
}
}).start();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

运行结果和上面一样。只是多了一个CallBack

其实还有Post请求,文件上传下载,图片加载,拦截器的使用,支持session的保持这里就先不说了。以后有时间再学习下。下面就是简单的来看下他的源码,只是以个人理解来分析下,看源码前先来了解一些基本的知识。

一些基本的知识

Http

Http是一种基于TCP/IP连接的一套网络通信协议,它是一种一应一答的请求,它分为Get和Post请求,Get请求获取得是静态页面,它可以把参数放在URL字符串后面。而Post请求就不同了,它是把参数放在Http请求的正文的。 
Get请求我们会这样请求:

   private void HttpURLConnection_Get(){
try{
//通过openConnection 连接
URL url = new java.net.URL(URL);
urlConn=(HttpURLConnection)url.openConnection();
//设置输入和输出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
//关闭连接
urlConn.disconnect();
}catch(Exception e){
resultData = "连接超时";
}
}

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

然后把获取到的urlConn连接的数据通过IO流把读取出来:

  InputStreamReader in = new InputStreamReader(urlConn.getInputStream());
BufferedReader buffer = new BufferedReader(in);
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){
resultData += inputLine + "\n";
}
System.out.println(resultData);
in.close();

1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

Post请求则会这样:

   private void HttpURLConnection_Post(){
try{
//通过openConnection 连接
URL url = new java.net.URL(URL_Post);
urlConn=(HttpURLConnection)url.openConnection();
//设置输入和输出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true); urlConn.setRequestMethod("POST");
urlConn.setUseCaches(false);
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
urlConn.connect();
//DataOutputStream流
DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());
//要上传的参数
String content = "par=" + URLEncoder.encode("ylx_Post+中正", "UTF_8");
//将要上传的内容写入流中
out.writeBytes(content);
//刷新、关闭
out.flush();
out.close(); }catch(Exception e){
resultData = "连接超时";
}
}

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
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

然后同上把获取到的urlConn连接的数据通过IO流把读取出来,大概的代码就是这样。

HTTPS 
HTTP加入SSL即是HTTPS,它安全性更高,HTTP使用得端口号是80,而HTTPS使用的端口号是443。HTTP协议以明文方式发送内容,不提供任何方式的数据加密。HTTPS协议一般需要到CA申请证书,当然也可以自签名证书,和12306一样。这里就说下HTTPS的核心SSL和TLS。 
首先我们来看一张简单的模型图

从这张图我们可以看出,最左边为经典的ISO7层模型图,右边我们可以看到有一个SSL层,它又叫做安全套捷字层,它分为SSL记录协议和SSL握手协议。SSL位于传输层和应用层之间,其中SSL记录 层协议位于传输层协议之上,而SSL握手协议又在SSL记录协议之上。SSL记录协议可以为高层协议进行加密,压缩,封装等功能,而SSL握手协议进行的是身份认证,协商加密算法、交换加密密钥等。其中TLS和SSL类似,它建立在SSL3.0协议之上。主要的不同在于他们的加密算法不同,其他功能作用类似。想要详情看他们的区别,请看这篇文章SSL与TLS的区别以及介绍。

基础基本上讲完了,现在就来说说OkHttp涉及到的一些知识了。支持SPDY协议和HTTP2.0协议,同步和异步请求,拦截机制,请求和响应的逻辑处理,缓存机制,重连和重定向机制,连接池,Gzip压缩,安全性,平台适应性等等。下面我们就来通过源码来一步步的学习。

源码分析:

支持哪些协议 
既然是与服务器之间的通信协议,我们应该会想到Protocol这个类,这个类是干嘛的呢?它主要是配置与远程服务器的通信协议。既然这样,那我们去源码找下这个类。其实在OkHttpClient源码里面第一个属性我们就可以看到

private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);

1
2
1
2

再进去Protocol类

public enum Protocol {
HTTP_1_0("http/1.0"),
HTTP_1_1("http/1.1"),
SPDY_3("spdy/3.1"),
HTTP_2("h2");
//省略部分代码
}

1
2
3
4
5
6
7
1
2
3
4
5
6
7

进入这个类,我们发现,这是一个枚举类,它定义了一些和远程服务器通信的协议名称,如上面四种。 
然后回到OkHttpClient这个类,跟踪protocols这个属性,我们会找到这个方法:

    public Builder protocols(List<Protocol> protocols) {
protocols = Util.immutableList(protocols);
if (!protocols.contains(Protocol.HTTP_1_1)) {
throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
}
if (protocols.contains(Protocol.HTTP_1_0)) {
throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
}
if (protocols.contains(null)) {
throw new IllegalArgumentException("protocols must not contain null");
}
this.protocols = Util.immutableList(protocols);
return this;
} public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) {
this.connectionSpecs = Util.immutableList(connectionSpecs);
return this;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

我们会发现,OkHttp是支持http/1.1版本的,但是不支持http/1.0版本的协议,支持h2协议,以及spdy/3.1协议。而且协议名称不能为null。既然支持h2,就说明服务端支持 ALPN的,它将可以协商到协商到 HTTP/2。这个很好呀,好在哪里呢,我们可以看下这篇文章,为什么我们应该尽快支持 ALPN?

同步和异步请求 
这个其实上面已经说到过了,OkHttp可以通过调用execute()实现同步请求,然后通过enqueue()方法可以实现异步请求。从上面的请求代码我们可以知道,它是通过okHttpClient.newCall(request)得到一个Call对象来调用的。那现在我们就先来看下Call这个类。

public interface Call {
//初始化这个请求并且返回这个请求
Request request();
//同步方法
Response execute() throws IOException;
//异步方法
void enqueue(Callback responseCallback);
//取消请求,完成的请求则不能取消
void cancel();
//省略部分代码
interface Factory {
Call newCall(Request request);
}
}

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

它是一个接口类,里面包含同步方法和异步方法,我们还可以看到定义了一个内部接口Factory ,实现这个接口,通过newCall方法返回Call对象,也就是上面我们调用的OkHttpClient.newCall(Request request),因为OkHttpClient对象已经实现这个接口。那么我们就回到OkHttpClient对象里面的newCall(Request request)方法里面。

@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}

1
2
3
4
1
2
3
4

创建了一个RealCall对象,那么同步异步方法肯定就在这里面实现了,继续来看看,RealCall实现了Call接口,果然是在这里,接下来看同步和异步方法。 
同步方法:

  @Override
public Response execute() throws IOException {
//同步操作,如果这个请求已经请求完了,则直接抛异常返回
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//通过dispatcher类来实现同步请求
client.dispatcher().executed(this);
//拦截器,下文再说
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

先不管Dispatcher类,先来看看异步请求方法:

void enqueue(Callback responseCallback, boolean forWebSocket) {
//同步操作,如果这个请求已经请求完了,则直接抛异常返回,省略这里代码
//然后是发出异步请求,也是通过Dispatcher类。而且这里增加了一个callback
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

1
2
3
4
5
1
2
3
4
5

还是等下来看Dispatcher类,先来看下AsyncCall接口,实现了NamedRunnable这个线程,这个线程会把当前”OkHttp “+请求url设置为当前线程名。接下来就是看下核心的execute抽象方法的实现

   @Override protected void execute() {
boolean signalledCallback = false;
try {
//也是会来到拦截器里面,下文说
Response response = getResponseWithInterceptorChain(forWebSocket);
//如果这个请求已经取消,则直接调用callback的失败接口。
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//直接调用callback的成功接口。
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
//异常,调用失败接口
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//异常,调用完成接口
client.dispatcher().finished(this);
}
}

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
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

其实主要还是回到拦截器里面,这里先不说拦截器,其他的也就是callback的逻辑处理。好了,现在就来说Dispatcher这个类了。上面我们说了同步和异步方法里面会去调用Dispatcher类的executed(this)和enqueue(AsyncCall call)方法。先来看下Dispatcher类的executed(this)

 synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
synchronized void finished(Call call) {
if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
}

1
2
3
4
5
6
1
2
3
4
5
6

Dispatcher类通过一个同步队列runningSyncCalls会帮我保存同步的请求Call。然后在完成请求之后再finished里面remove掉,其实runningSyncCalls的作用就是统计当前有多少个同步请求,其他作用还没发现。 
再来看下enqueue(AsyncCall call)方法:

synchronized void enqueue(AsyncCall call) {
//当前异步请求量小于请求量的最大值64,并且请求同一个host服务器地址小于5的条件下
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//把请求的AsyncCall记录到正在执行的请求队列runningAsyncCalls,并且通过线程池去执行这个请求。
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
/*超过最大请求量则添加到后备队列里面,等前面请求完成的时候,也就是调用finished(AsyncCall call)的时候。通过promoteCalls方法把readyAsyncCalls的请求添加到runningAsyncCalls执行*/
readyAsyncCalls.add(call);
}
}

1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11

把readyAsyncCalls的请求添加到runningAsyncCalls执行方法如下

  private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
} if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

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

上面的代码应该都看的懂。

拦截器机制 
先说说拦截器是用来干嘛的吧。六个字:监控,修改,添加。可以监控请求和响应日志,可以修改http请求信息,比如将域名替换为ip地址,将请求头中添加host属性。可以添加我们应用中的一些公共参数,比如设备id、版本号等等。 
先来个官网提供的监控请求和响应日志吧

   class LoggingInterceptors implements Interceptor{
private final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
@Override
public Response intercept(Chain chain) throws IOException { long t1 = System.nanoTime();
Request request = chain.request();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}

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

然后创建OkHttp的时候这样创建就好了

new OkHttpClient()
.newBuilder()
.addNetworkInterceptor(new LoggingInterceptors())
.build();

1
2
3
4
1
2
3
4

代码就不解释了,网上很多这个例子。 
再简单说下添加公共参数的例子。

    class LoggingInterceptors implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException { Request original = chain.request();
//添加请求头,可以添加多个参数,或者在original.body()http的body里面添加参数
Request.Builder requestBuilder = original.newBuilder()
.addHeader("device", Build.DEVICE)
.method(original.method(),original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
}

1
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

上面只是一个简单的参考,可以参考Okhttp中如何在拦截器中的RequestBody添加参数?,我们也可以和源码里面一样通过Builder的形式,向外公开一些add方法。 
其实网上也挺多这方面的例子的,拦截器还是很好用的,下面我们就来学下OkHttp源码里面的拦截器 
通过上面例子可以知道chain.proceed(request)直接然后的是http的响应请求,那么现在就来这个方法看下吧。

public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}

1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

我们可以看到有proceed这个方法。有两个拦截器实现这个接口,一个是应用拦截器ApplicationInterceptorChain,一个是网络拦截器NetworkInterceptorChain。那么怎么理解这两种拦截器呢,这里我们先来debug下。 
先来debug下addInterceptor这个方法,也就是应用拦截器。 

断点地方如图,我们可以看下调用proceed()方法的地方只有一个,也就是ApplicationInterceptorChain。而ApplicationInterceptorChain的proceed方法里面调用intercept方法,而NetworkInterceptorChain的proceed方法没走,这也是为什么上面例子打印一次的原因。如果不信的话,可以去NetworkInterceptorChain的proceed断点调试下,它是不走那里的。接下来我们就来看下.addNetworkInterceptor(),来调试下。

这里有两个地方调用了proceed(),说明了ApplicationInterceptorChain的proceed方法里面也调用intercept方法,可以去ApplicationInterceptorChain的proceed断点调试下,它还是会走那里。这也是上面例子打印两次的原因

所以可以把它理解为应用拦截器,是http发送请求的时候进行一次拦截,它不会走网络进行请求的。而网络拦截器我们可以把它看做是服务器响应的时候进行的一次拦截,它会走网络请求的。可以结合下图来理解 
 
来看下应用拦截器ApplicationInterceptorChain的process方法

    @Override public Response proceed(Request request) throws IOException {
//如果拦截链里面还有拦截器,则调用这里,也就是递归处理
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain); if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
} return interceptedResponse;
} //如果没有拦截器,则继续进行http请求
return getResponse(request, forWebSocket);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

继续来看下网络拦截器NetworkInterceptorChain的proceed方法。

    @Override public Response proceed(Request request) throws IOException {
//统计proceed调用次数
calls++; if (index > 0) {
Interceptor caller = client.networkInterceptors().get(index - 1);
Address address = connection().route().address(); //每次递归都会去判断url是否包含host和port。
if (!request.url().host().equals(address.url().host())
|| request.url().port() != address.url().port()) {
throw new IllegalStateException("network interceptor " + caller
+ " must retain the same host and port");
} // Confirm that this is the interceptor's first call to chain.proceed().
if (calls > 1) {
throw new IllegalStateException("network interceptor " + caller
+ " must call proceed() exactly once");
}
}
//递归NetworkInterceptorChain,直到拦截链里面没有拦截器
if (index < client.networkInterceptors().size()) {
NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
Interceptor interceptor = client.networkInterceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain); // Confirm that the interceptor made the required call to chain.proceed().
if (chain.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
if (interceptedResponse == null) {
throw new NullPointerException("network interceptor " + interceptor
+ " returned null");
} return interceptedResponse;
}
//把request写入http头部里面
httpStream.writeRequestHeaders(request); //更新的networkRequest,可能拦截已经请求更新
networkRequest = request;
//这里是对post请求进行的一些body写入
if (permitsRequestBody(request) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
//通过io包进行一些io流操作
Response response = readNetworkResponse(); int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
} return 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
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

其实拦截器还有个stetho工具,集成它使用拦截器获取日志就更方便了。集成教程网上有很多。

请求和响应的逻辑处理 
其实上面我们在proceed方法里面已经提到了这块,它是通过getResponse()方法返回Response的,其实发送网络请求和得到返回的响应都是在这里面进行逻辑处理的,如果中途取消请求的时候,getResponse()返回null

  Response getResponse(Request request, boolean forWebSocket) throws IOException {
//...省略前面代码
try {
//发送请求
engine.sendRequest();
//回复的响应
engine.readResponse();
releaseConnection = false;
}
//...省略后面代码

1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10

从上面可以知道,请求和响应的逻辑处理是在HttpEngine对象的,接下来就来看下HttpEngine对象 
这里是发送一个请求方法

  public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代码
//通过Request.Builder设置Request的配置信息,然后返回配置好的Request 对象
Request request = networkRequest(userRequest);
//...省略部分代码
//将request 传入缓存的处理类里面进行一些缓存处理,然后返回networkRequest ,其实和request 一样
networkRequest = cacheStrategy.networkRequest;
//...省略部分代码
try {
//真正的通过socket通信发送请求出去
httpStream = connect();
httpStream.setHttpEngine(this);
//如果是post或者带有body的请求方式,执行下面部分写出body
if (writeRequestHeaders()) {
long contentLength = OkHeaders.contentLength(request);
if (bufferRequestBody) {
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
} if (contentLength != -1) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}

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
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

读取响应的方法

  public void readResponse() throws IOException {
//...省略部分代码
if (forWebSocket) {
httpStream.writeRequestHeaders(networkRequest);
networkResponse = readNetworkResponse();
} else if (!callerWritesRequestBody) { networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
} else {
// Emit the request body's buffer so that everything is in requestBodyOut.
if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
bufferedRequestBody.emit();
}
//...省略部分代码
//其实真正的我们还是通过这个方式来获取响应数据的
networkResponse = readNetworkResponse();
}
//...省略部分代码
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

接下来就来看看readNetworkResponse这个方法

  private Response readNetworkResponse() throws IOException {
httpStream.finishRequest();
//这里通过io流去读取响应的数据
Response networkResponse = httpStream.readResponseHeaders()
.request(networkRequest)
.handshake(streamAllocation.connection().handshake())
.header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
.header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
.build(); if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
} if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
|| "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
streamAllocation.noNewStreams();
} return networkResponse;
}

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

其实发送请求这块还有很多源码的处理,其他的就先不看了,基本上是一些io流的处理了。

缓存处理 
先来看看OkHttp的缓存怎么设置

   int cacheSize = 10 * 1024 * 1024; // 10 MiB
//cacheDirectory保存缓存的目录,cacheSize缓存空间的大小
Cache cache = new Cache(context.getCacheDir(), cacheSize);
final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.cache(cache)
.build();

1
2
3
4
5
6
7
1
2
3
4
5
6
7

这样我们就设置好了。下面我们就来分析下源码,其实在上面发送请求和读取响应方法里面已经有缓存处理的逻辑。回到sendRequest()方法

 public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代码
/*Internal是一个抽象类,定义了很多个抽象类,其中就有setCache(OkHttpClient.Builder builder, InternalCache internalCache)这个方法,然后.internalCache(client)其实它会去调用OkHttpClient里的static块里的Internal的internalCache方法,返回一个InternalCache*/
InternalCache responseCache = Internal.instance.internalCache(client);
//Cache类获取缓存里面的响应数据
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null; long now = System.currentTimeMillis();
//创建CacheStrategy.Factory对象,进行缓存配置
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
//传入的网络连接
networkRequest = cacheStrategy.networkRequest;
//cacheCandidate 传入CacheStrategy后得到的缓存的响应数据
cacheResponse = cacheStrategy.cacheResponse; if (responseCache != null) {
//记录当前请求是网络发起还是缓存发起
responseCache.trackResponse(cacheStrategy);
}
//如果传入CacheStrategy不可用并且cacheResponse 为null,结束所有请求连接资源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果网络连接被禁止访问并且缓存为null的时候
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
} // 如果没有网络的情况下,这时候缓存是不为null的,所以这里就去获取缓存里面的数据
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
} //...省略部分代码
}

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
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

上面的返回的InternalCache 接口定义在了Cache这个类里面,我们可以看到OkHttp使用的缓存是DiskLruCache,详细缓存处理就不说了。

  public void readResponse() throws IOException {
//...省略部分代码
if (cacheResponse != null) {
//检查缓存是否可用,如果可用。那么就用当前缓存的Response,关闭网络连接,释放连接。
if (validate(cacheResponse, networkResponse)) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation(); // headers去掉Content-Encoding之后更新缓存
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
} userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build(); if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}

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
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

其实缓存这块还不是特别理解,由于篇幅比较长了,而且这篇是初识篇,其实还有很多没去学习,比如重连和重定向机制,连接池,Gzip压缩,安全性,平台适应性,cookie的保持等等的知识,下次抽时间再来学习下了。

概括

OkHttp现在很火呀。于是上个星期就一直在学习OkHttp框架,虽然说起来已经有点晚上手了,貌似是2013年就推出了。但是现在它版本更加稳定了呀。这不,说着说着,OkHttp3.3版本在这几天又发布了。以下以OkHttp3.2版本为准,没办法,上个星期看的时候还是以3.2为最新版本的。首先,我们要先了解一些背景,OkHttp这个框架是有Square公司推出的,进入官网。如果想看API,点击进入API。大概了解了OkHttp之后,我们应该知道OkHttp是一个网络框架,想想以前在开发中,网络框架一般用的是什么?很快我们就会想到刚学习Android开发的时候接触的HttpURLConnection和Apache提供的HttpClient这两个类,然后就是后面推出的一些第三方网络框架,比如2013年google推出的Volley框架、android-async-http框架、2014年很火的Xutils、以及现在很多人用的Retrofit等等。这么多,到底选哪个?一开始我也晕。后来看了一些资料,似乎懂了一个概念:OkHttp是用来替换HttpURLConnection的,据说android4.4源码的HttpURLConnection就替换成了OkHttp。所以我们别拿OkHttp和这些网络框架比,这些网络框架也只是基于HttpURLConnection进行一些封装,使我们的代码更加简洁方便。懂了这点,我们应该就懂了为什么网上那么多OkHttp和Volley或者Retrofit等等这些框架结合使用了,其实是一个道理。那么我用的HttpUrlConnection或者HttpClient用的好好的,干嘛要用你的OkHttp?这里就来比较下HttpURLConnection和OkHttp。至于HttpClient嘛,android6.0已经把它的API废除了。用它还要引入org.apache.http.legacy.jar包,不值得,而且okhttp也已经提供了对应的okhttp-apache 模块。

HttpURLConnection和OkHttp的比较

HttpURLConnection有的API,OkHttp基本上都有(你有我有全都有呀,哈哈哈)
HttpURLConnection和OkHttp都支持Https,流的上传和下载,超时,IP6、连接池等等

OkHttp比HttpURLConnection具有更好的同步异步请求、缓存机制,支持HttpDNS、重定向、Gzip压缩,平台适应性、很好的服务器IP的转换、直接Socket通信,支持拦截器等等。

看到这么多机制,是不是觉得很强大,通过Socket直接通信,以及很好的缓存机制,Gzip对于Http头部的压缩传输。自然对于网络请求这块使应用更加省流量、请求的更快。OkHttp对于Https和HttpDNS的支持,使得应用的网络通信安全性更高。当然说了它的好,现在也来说说它的 
不好之处

OkHttp不支持优先级请求
OkHttp不支持自签名证书
OkHttp header中不能传中文

虽然是不好的地方,但是OkHttp已经比较成熟了,网上解决这几个问题的资料也很多了。所以这些都不是问题。

一个简单的Get请求例子

这里我们就以经典的官网提供的Get请求的例子来学习下,说大概的代码。

先在manifest加个网络权限,养成良好习惯

 <uses-permission android:name="android.permission.INTERNET"/>

1
1

然后在build.gradle文件的dependencies添加库如下:

dependencies {
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
}

1
2
3
4
1
2
3
4

同步Get请求:

      final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.build();
final Request request = new Request.Builder()
.url("https://www.publicobject.com/helloworld.txt")
.header("User-Agent","OkHttp Example")
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = okHttpClient.newCall(request).execute();
Log.d("zgx","response====="+response.body().string());
response.body().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这个例子网上说烂了,没啥说的,来看下结果 

很漂亮的样子。呵呵

异步Get请求: 
修改上面部分代码,Call类调用enqueue方法。代码如下:

  new Thread(new Runnable() {
@Override
public void run() {
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("zgx","response====="+e.getMessage());
} @Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("zgx","response====="+response.body().string());
response.body().close();
}
});
}
}).start();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

运行结果和上面一样。只是多了一个CallBack

其实还有Post请求,文件上传下载,图片加载,拦截器的使用,支持session的保持这里就先不说了。以后有时间再学习下。下面就是简单的来看下他的源码,只是以个人理解来分析下,看源码前先来了解一些基本的知识。

一些基本的知识

Http

Http是一种基于TCP/IP连接的一套网络通信协议,它是一种一应一答的请求,它分为Get和Post请求,Get请求获取得是静态页面,它可以把参数放在URL字符串后面。而Post请求就不同了,它是把参数放在Http请求的正文的。 
Get请求我们会这样请求:

   private void HttpURLConnection_Get(){
try{
//通过openConnection 连接
URL url = new java.net.URL(URL);
urlConn=(HttpURLConnection)url.openConnection();
//设置输入和输出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
//关闭连接
urlConn.disconnect();
}catch(Exception e){
resultData = "连接超时";
}
}

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

然后把获取到的urlConn连接的数据通过IO流把读取出来:

  InputStreamReader in = new InputStreamReader(urlConn.getInputStream());
BufferedReader buffer = new BufferedReader(in);
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){
resultData += inputLine + "\n";
}
System.out.println(resultData);
in.close();

1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

Post请求则会这样:

   private void HttpURLConnection_Post(){
try{
//通过openConnection 连接
URL url = new java.net.URL(URL_Post);
urlConn=(HttpURLConnection)url.openConnection();
//设置输入和输出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true); urlConn.setRequestMethod("POST");
urlConn.setUseCaches(false);
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
urlConn.connect();
//DataOutputStream流
DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());
//要上传的参数
String content = "par=" + URLEncoder.encode("ylx_Post+中正", "UTF_8");
//将要上传的内容写入流中
out.writeBytes(content);
//刷新、关闭
out.flush();
out.close(); }catch(Exception e){
resultData = "连接超时";
}
}

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
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

然后同上把获取到的urlConn连接的数据通过IO流把读取出来,大概的代码就是这样。

HTTPS 
HTTP加入SSL即是HTTPS,它安全性更高,HTTP使用得端口号是80,而HTTPS使用的端口号是443。HTTP协议以明文方式发送内容,不提供任何方式的数据加密。HTTPS协议一般需要到CA申请证书,当然也可以自签名证书,和12306一样。这里就说下HTTPS的核心SSL和TLS。 
首先我们来看一张简单的模型图

从这张图我们可以看出,最左边为经典的ISO7层模型图,右边我们可以看到有一个SSL层,它又叫做安全套捷字层,它分为SSL记录协议和SSL握手协议。SSL位于传输层和应用层之间,其中SSL记录 层协议位于传输层协议之上,而SSL握手协议又在SSL记录协议之上。SSL记录协议可以为高层协议进行加密,压缩,封装等功能,而SSL握手协议进行的是身份认证,协商加密算法、交换加密密钥等。其中TLS和SSL类似,它建立在SSL3.0协议之上。主要的不同在于他们的加密算法不同,其他功能作用类似。想要详情看他们的区别,请看这篇文章SSL与TLS的区别以及介绍。

基础基本上讲完了,现在就来说说OkHttp涉及到的一些知识了。支持SPDY协议和HTTP2.0协议,同步和异步请求,拦截机制,请求和响应的逻辑处理,缓存机制,重连和重定向机制,连接池,Gzip压缩,安全性,平台适应性等等。下面我们就来通过源码来一步步的学习。

源码分析:

支持哪些协议 
既然是与服务器之间的通信协议,我们应该会想到Protocol这个类,这个类是干嘛的呢?它主要是配置与远程服务器的通信协议。既然这样,那我们去源码找下这个类。其实在OkHttpClient源码里面第一个属性我们就可以看到

private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);

1
2
1
2

再进去Protocol类

public enum Protocol {
HTTP_1_0("http/1.0"),
HTTP_1_1("http/1.1"),
SPDY_3("spdy/3.1"),
HTTP_2("h2");
//省略部分代码
}

1
2
3
4
5
6
7
1
2
3
4
5
6
7

进入这个类,我们发现,这是一个枚举类,它定义了一些和远程服务器通信的协议名称,如上面四种。 
然后回到OkHttpClient这个类,跟踪protocols这个属性,我们会找到这个方法:

    public Builder protocols(List<Protocol> protocols) {
protocols = Util.immutableList(protocols);
if (!protocols.contains(Protocol.HTTP_1_1)) {
throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
}
if (protocols.contains(Protocol.HTTP_1_0)) {
throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
}
if (protocols.contains(null)) {
throw new IllegalArgumentException("protocols must not contain null");
}
this.protocols = Util.immutableList(protocols);
return this;
} public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) {
this.connectionSpecs = Util.immutableList(connectionSpecs);
return this;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

我们会发现,OkHttp是支持http/1.1版本的,但是不支持http/1.0版本的协议,支持h2协议,以及spdy/3.1协议。而且协议名称不能为null。既然支持h2,就说明服务端支持 ALPN的,它将可以协商到协商到 HTTP/2。这个很好呀,好在哪里呢,我们可以看下这篇文章,为什么我们应该尽快支持 ALPN?

同步和异步请求 
这个其实上面已经说到过了,OkHttp可以通过调用execute()实现同步请求,然后通过enqueue()方法可以实现异步请求。从上面的请求代码我们可以知道,它是通过okHttpClient.newCall(request)得到一个Call对象来调用的。那现在我们就先来看下Call这个类。

public interface Call {
//初始化这个请求并且返回这个请求
Request request();
//同步方法
Response execute() throws IOException;
//异步方法
void enqueue(Callback responseCallback);
//取消请求,完成的请求则不能取消
void cancel();
//省略部分代码
interface Factory {
Call newCall(Request request);
}
}

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

它是一个接口类,里面包含同步方法和异步方法,我们还可以看到定义了一个内部接口Factory ,实现这个接口,通过newCall方法返回Call对象,也就是上面我们调用的OkHttpClient.newCall(Request request),因为OkHttpClient对象已经实现这个接口。那么我们就回到OkHttpClient对象里面的newCall(Request request)方法里面。

@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}

1
2
3
4
1
2
3
4

创建了一个RealCall对象,那么同步异步方法肯定就在这里面实现了,继续来看看,RealCall实现了Call接口,果然是在这里,接下来看同步和异步方法。 
同步方法:

  @Override
public Response execute() throws IOException {
//同步操作,如果这个请求已经请求完了,则直接抛异常返回
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//通过dispatcher类来实现同步请求
client.dispatcher().executed(this);
//拦截器,下文再说
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

先不管Dispatcher类,先来看看异步请求方法:

void enqueue(Callback responseCallback, boolean forWebSocket) {
//同步操作,如果这个请求已经请求完了,则直接抛异常返回,省略这里代码
//然后是发出异步请求,也是通过Dispatcher类。而且这里增加了一个callback
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

1
2
3
4
5
1
2
3
4
5

还是等下来看Dispatcher类,先来看下AsyncCall接口,实现了NamedRunnable这个线程,这个线程会把当前”OkHttp “+请求url设置为当前线程名。接下来就是看下核心的execute抽象方法的实现

   @Override protected void execute() {
boolean signalledCallback = false;
try {
//也是会来到拦截器里面,下文说
Response response = getResponseWithInterceptorChain(forWebSocket);
//如果这个请求已经取消,则直接调用callback的失败接口。
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//直接调用callback的成功接口。
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
//异常,调用失败接口
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//异常,调用完成接口
client.dispatcher().finished(this);
}
}

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
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

其实主要还是回到拦截器里面,这里先不说拦截器,其他的也就是callback的逻辑处理。好了,现在就来说Dispatcher这个类了。上面我们说了同步和异步方法里面会去调用Dispatcher类的executed(this)和enqueue(AsyncCall call)方法。先来看下Dispatcher类的executed(this)

 synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
synchronized void finished(Call call) {
if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
}

1
2
3
4
5
6
1
2
3
4
5
6

Dispatcher类通过一个同步队列runningSyncCalls会帮我保存同步的请求Call。然后在完成请求之后再finished里面remove掉,其实runningSyncCalls的作用就是统计当前有多少个同步请求,其他作用还没发现。 
再来看下enqueue(AsyncCall call)方法:

synchronized void enqueue(AsyncCall call) {
//当前异步请求量小于请求量的最大值64,并且请求同一个host服务器地址小于5的条件下
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//把请求的AsyncCall记录到正在执行的请求队列runningAsyncCalls,并且通过线程池去执行这个请求。
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
/*超过最大请求量则添加到后备队列里面,等前面请求完成的时候,也就是调用finished(AsyncCall call)的时候。通过promoteCalls方法把readyAsyncCalls的请求添加到runningAsyncCalls执行*/
readyAsyncCalls.add(call);
}
}

1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11

把readyAsyncCalls的请求添加到runningAsyncCalls执行方法如下

  private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
} if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

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

上面的代码应该都看的懂。

拦截器机制 
先说说拦截器是用来干嘛的吧。六个字:监控,修改,添加。可以监控请求和响应日志,可以修改http请求信息,比如将域名替换为ip地址,将请求头中添加host属性。可以添加我们应用中的一些公共参数,比如设备id、版本号等等。 
先来个官网提供的监控请求和响应日志吧

   class LoggingInterceptors implements Interceptor{
private final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
@Override
public Response intercept(Chain chain) throws IOException { long t1 = System.nanoTime();
Request request = chain.request();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}

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

然后创建OkHttp的时候这样创建就好了

new OkHttpClient()
.newBuilder()
.addNetworkInterceptor(new LoggingInterceptors())
.build();

1
2
3
4
1
2
3
4

代码就不解释了,网上很多这个例子。 
再简单说下添加公共参数的例子。

    class LoggingInterceptors implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException { Request original = chain.request();
//添加请求头,可以添加多个参数,或者在original.body()http的body里面添加参数
Request.Builder requestBuilder = original.newBuilder()
.addHeader("device", Build.DEVICE)
.method(original.method(),original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
}

1
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

上面只是一个简单的参考,可以参考Okhttp中如何在拦截器中的RequestBody添加参数?,我们也可以和源码里面一样通过Builder的形式,向外公开一些add方法。 
其实网上也挺多这方面的例子的,拦截器还是很好用的,下面我们就来学下OkHttp源码里面的拦截器 
通过上面例子可以知道chain.proceed(request)直接然后的是http的响应请求,那么现在就来这个方法看下吧。

public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}

1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

我们可以看到有proceed这个方法。有两个拦截器实现这个接口,一个是应用拦截器ApplicationInterceptorChain,一个是网络拦截器NetworkInterceptorChain。那么怎么理解这两种拦截器呢,这里我们先来debug下。 
先来debug下addInterceptor这个方法,也就是应用拦截器。 

断点地方如图,我们可以看下调用proceed()方法的地方只有一个,也就是ApplicationInterceptorChain。而ApplicationInterceptorChain的proceed方法里面调用intercept方法,而NetworkInterceptorChain的proceed方法没走,这也是为什么上面例子打印一次的原因。如果不信的话,可以去NetworkInterceptorChain的proceed断点调试下,它是不走那里的。接下来我们就来看下.addNetworkInterceptor(),来调试下。

这里有两个地方调用了proceed(),说明了ApplicationInterceptorChain的proceed方法里面也调用intercept方法,可以去ApplicationInterceptorChain的proceed断点调试下,它还是会走那里。这也是上面例子打印两次的原因

所以可以把它理解为应用拦截器,是http发送请求的时候进行一次拦截,它不会走网络进行请求的。而网络拦截器我们可以把它看做是服务器响应的时候进行的一次拦截,它会走网络请求的。可以结合下图来理解 
 
来看下应用拦截器ApplicationInterceptorChain的process方法

    @Override public Response proceed(Request request) throws IOException {
//如果拦截链里面还有拦截器,则调用这里,也就是递归处理
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain); if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
} return interceptedResponse;
} //如果没有拦截器,则继续进行http请求
return getResponse(request, forWebSocket);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

继续来看下网络拦截器NetworkInterceptorChain的proceed方法。

    @Override public Response proceed(Request request) throws IOException {
//统计proceed调用次数
calls++; if (index > 0) {
Interceptor caller = client.networkInterceptors().get(index - 1);
Address address = connection().route().address(); //每次递归都会去判断url是否包含host和port。
if (!request.url().host().equals(address.url().host())
|| request.url().port() != address.url().port()) {
throw new IllegalStateException("network interceptor " + caller
+ " must retain the same host and port");
} // Confirm that this is the interceptor's first call to chain.proceed().
if (calls > 1) {
throw new IllegalStateException("network interceptor " + caller
+ " must call proceed() exactly once");
}
}
//递归NetworkInterceptorChain,直到拦截链里面没有拦截器
if (index < client.networkInterceptors().size()) {
NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
Interceptor interceptor = client.networkInterceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain); // Confirm that the interceptor made the required call to chain.proceed().
if (chain.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
if (interceptedResponse == null) {
throw new NullPointerException("network interceptor " + interceptor
+ " returned null");
} return interceptedResponse;
}
//把request写入http头部里面
httpStream.writeRequestHeaders(request); //更新的networkRequest,可能拦截已经请求更新
networkRequest = request;
//这里是对post请求进行的一些body写入
if (permitsRequestBody(request) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
//通过io包进行一些io流操作
Response response = readNetworkResponse(); int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
} return 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
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

其实拦截器还有个stetho工具,集成它使用拦截器获取日志就更方便了。集成教程网上有很多。

请求和响应的逻辑处理 
其实上面我们在proceed方法里面已经提到了这块,它是通过getResponse()方法返回Response的,其实发送网络请求和得到返回的响应都是在这里面进行逻辑处理的,如果中途取消请求的时候,getResponse()返回null

  Response getResponse(Request request, boolean forWebSocket) throws IOException {
//...省略前面代码
try {
//发送请求
engine.sendRequest();
//回复的响应
engine.readResponse();
releaseConnection = false;
}
//...省略后面代码

1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10

从上面可以知道,请求和响应的逻辑处理是在HttpEngine对象的,接下来就来看下HttpEngine对象 
这里是发送一个请求方法

  public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代码
//通过Request.Builder设置Request的配置信息,然后返回配置好的Request 对象
Request request = networkRequest(userRequest);
//...省略部分代码
//将request 传入缓存的处理类里面进行一些缓存处理,然后返回networkRequest ,其实和request 一样
networkRequest = cacheStrategy.networkRequest;
//...省略部分代码
try {
//真正的通过socket通信发送请求出去
httpStream = connect();
httpStream.setHttpEngine(this);
//如果是post或者带有body的请求方式,执行下面部分写出body
if (writeRequestHeaders()) {
long contentLength = OkHeaders.contentLength(request);
if (bufferRequestBody) {
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
} if (contentLength != -1) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}

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
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

读取响应的方法

  public void readResponse() throws IOException {
//...省略部分代码
if (forWebSocket) {
httpStream.writeRequestHeaders(networkRequest);
networkResponse = readNetworkResponse();
} else if (!callerWritesRequestBody) { networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
} else {
// Emit the request body's buffer so that everything is in requestBodyOut.
if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
bufferedRequestBody.emit();
}
//...省略部分代码
//其实真正的我们还是通过这个方式来获取响应数据的
networkResponse = readNetworkResponse();
}
//...省略部分代码
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

接下来就来看看readNetworkResponse这个方法

  private Response readNetworkResponse() throws IOException {
httpStream.finishRequest();
//这里通过io流去读取响应的数据
Response networkResponse = httpStream.readResponseHeaders()
.request(networkRequest)
.handshake(streamAllocation.connection().handshake())
.header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
.header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
.build(); if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
} if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
|| "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
streamAllocation.noNewStreams();
} return networkResponse;
}

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

其实发送请求这块还有很多源码的处理,其他的就先不看了,基本上是一些io流的处理了。

缓存处理 
先来看看OkHttp的缓存怎么设置

   int cacheSize = 10 * 1024 * 1024; // 10 MiB
//cacheDirectory保存缓存的目录,cacheSize缓存空间的大小
Cache cache = new Cache(context.getCacheDir(), cacheSize);
final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.cache(cache)
.build();

1
2
3
4
5
6
7
1
2
3
4
5
6
7

这样我们就设置好了。下面我们就来分析下源码,其实在上面发送请求和读取响应方法里面已经有缓存处理的逻辑。回到sendRequest()方法

 public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代码
/*Internal是一个抽象类,定义了很多个抽象类,其中就有setCache(OkHttpClient.Builder builder, InternalCache internalCache)这个方法,然后.internalCache(client)其实它会去调用OkHttpClient里的static块里的Internal的internalCache方法,返回一个InternalCache*/
InternalCache responseCache = Internal.instance.internalCache(client);
//Cache类获取缓存里面的响应数据
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null; long now = System.currentTimeMillis();
//创建CacheStrategy.Factory对象,进行缓存配置
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
//传入的网络连接
networkRequest = cacheStrategy.networkRequest;
//cacheCandidate 传入CacheStrategy后得到的缓存的响应数据
cacheResponse = cacheStrategy.cacheResponse; if (responseCache != null) {
//记录当前请求是网络发起还是缓存发起
responseCache.trackResponse(cacheStrategy);
}
//如果传入CacheStrategy不可用并且cacheResponse 为null,结束所有请求连接资源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果网络连接被禁止访问并且缓存为null的时候
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
} // 如果没有网络的情况下,这时候缓存是不为null的,所以这里就去获取缓存里面的数据
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
} //...省略部分代码
}

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
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

上面的返回的InternalCache 接口定义在了Cache这个类里面,我们可以看到OkHttp使用的缓存是DiskLruCache,详细缓存处理就不说了。

  public void readResponse() throws IOException {
//...省略部分代码
if (cacheResponse != null) {
//检查缓存是否可用,如果可用。那么就用当前缓存的Response,关闭网络连接,释放连接。
if (validate(cacheResponse, networkResponse)) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation(); // headers去掉Content-Encoding之后更新缓存
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
} userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build(); if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}

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
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

其实缓存这块还不是特别理解,由于篇幅比较长了,而且这篇是初识篇,其实还有很多没去学习,比如重连和重定向机制,连接池,Gzip压缩,安全性,平台适应性,cookie的保持等等的知识,下次抽时间再来学习下了。

Android网络框架OkHttp之get请求(源码初识)的相关教程结束。

《Android网络框架OkHttp之get请求(源码初识).doc》

下载本文的Word格式文档,以方便收藏与打印。