OkHttp源码解析

来源:互联网 发布:家庭相册制作软件 编辑:程序博客网 时间:2024/06/17 04:00

转:http://mp.weixin.qq.com/s?__biz=MzIyOTUzNDk0NQ%3D%3D&idx=1&mid=2247484211&sn=a2e2fc03d32accc061075dd58b62a32e

由于okhttp目前太流行了,甚至很多开源网络框架底层都是使用它做了二次封装。因此很有必要对它的原理进行深入的分析。

本文没办法一篇盖全,如果要分析的很透彻或许一本书也介绍不完。想了解更多,读者可以去网络参考更多文章和书籍,而且本文也只分析get请求。


总体架构(这部分可以看完文章再回来看)

上图是OkHttp的总体架构,大致可以分为以下几层:

  • Interface——接口层:接受网络访问请求

  • Protocol——协议层:处理协议逻辑

  • Connection——连接层:管理网络连接,发送新的请求,接收服务器访问

  • Cache——缓存层:管理本地缓存

  • I/O——I/O层:实际数据读写实现

  • Inteceptor——拦截器层:拦截网络访问,插入拦截逻辑

Interface——接口层:

接口层接收用户的网络访问请求(同步请求/异步请求),发起实际的网络访问。OkHttpClient是OkHttp框架的客户端,更确切的说是一个用户面板。用户使用OkHttp进行各种设置,发起各种网络请求都是通过OkHttpClient完成的。每个OkHttpClient内部都维护了属于自己的任务队列,连接池,Cache,拦截器等,所以在使用OkHttp作为网络框架时应该全局共享一个OkHttpClient实例。

Call描述一个实际的访问请求,用户的每一个网络请求都是一个Call实例。Call本身只是一个接口,定义了Call的接口方法,实际执行过程中,OkHttp会为每一个请求创建一个RealCall,对于异步请求每一个RealCall内部有一个AsyncCall

每一个Call就是一个线程,而执行Call的过程就是执行其execute方法的过程。

Dispatcher是OkHttp的任务队列,其内部维护了一个线程池,当有接收到一个Call时,Dispatcher负责在线程池中找到空闲的线程并执行其execute方法。

Protocol——协议层:处理协议逻辑

Protocol层负责处理协议逻辑,OkHttp支持Http1/Http2/WebSocket协议,并在3.7版本中放弃了对Spdy协议,鼓励开发者使用Http/2。

Connection——连接层:管理网络连接,发送新的请求,接收服务器访问

连接层顾名思义就是负责网络连接。在连接层中有一个连接池,统一管理所有的Socket连接,当用户新发起一个网络请求时,OkHttp会首先从连接池中查找是否有符合要求的连接,如果有则直接通过该连接发送网络请求;否则新创建一个网络连接。

RealConnection描述一个物理Socket连接,连接池中维护多个RealConnection实例。由于Http/2支持多路复用,一个RealConnection可以支持多个网络访问请求,所以OkHttp又引入了StreamAllocation来描述一个实际的网络请求开销(从逻辑上一个Stream对应一个Call,但在实际网络请求过程中一个Call常常涉及到多次请求。如重定向,Authenticate等场景。所以准确地说,一个Stream对应一次请求,而一个Call对应一组有逻辑关联的Stream),一个RealConnection对应一个或多个StreamAllocation,所以StreamAllocation可以看做是RealConenction的计数器,当RealConnection的引用计数变为0,且长时间没有被其他请求重新占用就将被释放。

Cache——缓存层:管理本地缓存

Cache层负责维护请求缓存,当用户的网络请求在本地已有符合要求的缓存时,OkHttp会直接从缓存中返回结果,从而节省网络开销。

I/O——I/O层:实际数据读写实现

I/O层负责实际的数据读写。OkHttp的另一大有点就是其高效的I/O操作,这归因于其高效的I/O库okio

Inteceptor——拦截器层:拦截网络访问,插入拦截逻辑

拦截器层提供了一个类AOP接口,方便用户可以切入到各个层面对网络访问进行拦截并执行相关逻辑。从每个拦截器调下一个拦截器,最后真正访问网络获取数据在最后一个拦截器CallServerInterceptor。然后在逐一返回,把response对象一层层返回回去。


文章主要从以下几个方面来分析okhttp的源码:

1、基本使用

2、源码的请求过程


1、基本使用:

对于okHttp的基本使用相信大家已经烂熟于心,最基本的使用时get和post以及同步和异步请求。由于网络访问大部分情况都是使用异步请求方式。

首先要初始化OkhttpClient实例,然后,构建Request实例

如果要同步请求:


如果要异步请求:

使用很简单:

1)、实例化OkhttpClient;2)、创建请求Request;3)、执行请求返回数据Response;4)从响应中取出数据。

如果是异步的话,上面的3)、客户端对象添加回调;4)、在回调中取出数据


2、源码的请求过程

首先,在初始化的时候:

这个初始化的过程很好理解,点击源码:

OkhttpClient:

public OkHttpClient() {//构造方法    this(new Builder());//调用有参构造,并传入Builder对象  }  OkHttpClient(Builder builder) {//把Builder中的属性做一个转接    this.dispatcher = builder.dispatcher;    this.proxy = builder.proxy;    this.protocols = builder.protocols;    this.connectionSpecs = builder.connectionSpecs;    this.interceptors = Util.immutableList(builder.interceptors);    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);    this.proxySelector = builder.proxySelector;    this.cookieJar = builder.cookieJar;    this.cache = builder.cache;    this.internalCache = builder.internalCache;    this.socketFactory = builder.socketFactory;    boolean isTLS = false;    for (ConnectionSpec spec : connectionSpecs) {      isTLS = isTLS || spec.isTls();    }    if (builder.sslSocketFactory != null || !isTLS) {      this.sslSocketFactory = builder.sslSocketFactory;      this.certificateChainCleaner = builder.certificateChainCleaner;    } else {      X509TrustManager trustManager = systemDefaultTrustManager();      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);    }    this.hostnameVerifier = builder.hostnameVerifier;    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(        certificateChainCleaner);    this.proxyAuthenticator = builder.proxyAuthenticator;    this.authenticator = builder.authenticator;    this.connectionPool = builder.connectionPool;    this.dns = builder.dns;    this.followSslRedirects = builder.followSslRedirects;    this.followRedirects = builder.followRedirects;    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;    this.connectTimeout = builder.connectTimeout;    this.readTimeout = builder.readTimeout;    this.writeTimeout = builder.writeTimeout;    this.pingInterval = builder.pingInterval;  }

文中注释也给出了,首先调用无参构造,而里面调用了有参构造,且传入了一个Builder对象,并在里面把Builder的属性做了一个转接。也就是说,我们如果没有设置参数的时候,在Builder里面会默认的初始化:


    final Dispatcher dispatcher;  //分发器    final Proxy proxy;  //代理    final List<Protocol> protocols; //协议    final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议    final List<Interceptor> interceptors; //拦截器    final List<Interceptor> networkInterceptors; //网络拦截器    final ProxySelector proxySelector; //代理选择    final CookieJar cookieJar; //cookie    final Cache cache; //缓存    final InternalCache internalCache;  //内部缓存    final SocketFactory socketFactory;  //socket 工厂    final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS    final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。    final HostnameVerifier hostnameVerifier;    //  主机名字确认    final CertificatePinner certificatePinner;  //  证书链    final Authenticator proxyAuthenticator;     //代理身份验证    final Authenticator authenticator;      // 本地身份验证    final ConnectionPool connectionPool;    //连接池,复用连接    final Dns dns;  //域名    final boolean followSslRedirects;  //安全套接层重定向    final boolean followRedirects;  //本地重定向    final boolean retryOnConnectionFailure; //重试连接失败    final int connectTimeout;    //连接超时    final int readTimeout; //read 超时    final int writeTimeout; //write 超时


Builder是一个静态内部类,且final修饰。我们实例化静态内部类的时候一般如下:mClient = new OkHttpClient.Builder().build(); 

上面创建OkhttpClient也是可以的。

紧接着往下:

这里肯定不用讲了吧,Builder又是一个静态内部类,通过这种方式不但可以创建静态内部类对象,还可以调用静态内部类的方法做拼接,最后调用builder()发挥一个Request对象。这的确很Builder(构建者模式)。

点击去看一看:

静态内部类持有了五个实例,在无参构造里面默认请求方式为GET,我们在创建Request的时候,也可以拼接指明为GET请求:

然后创建一个请求头,它又是Headers类的静态内部类:

默认能够加入20个请求头。

然后再回到最初创建Request实例:

.url(url);它表示调用Request类的静态内部类的Builder的url方法,传入我们的url,它会在这个方法里面对url的合法性进行校验,并调用自己的url重载方法:

如果url为空,会抛出空指针异常,同时把url在内存中保存起来,返回this,这就是构建者模式的魅力之处吧!

然后再回到Activity初始化的时候,调用builder()方法,在这里就是new了一个Request实例嘛,当然,再一次做了url为空的校验:

到目前为止,初始化工作已经做完了,相信你会有一个比较清晰的认识了吧。

紧接着,就是开始真正的网络请求了,首先咱们先分析同步访问方法:

又是如此的简单,越是简单,越复杂,我们需要一点点来分析:

首先调用OkhttpClient方法内部的newCall方法,并把请求体实例Request传入进去,而本质上把参数给了RealCall,并把它的实例返回,返回类型是一个接口类型Call。这个Call接口封装了一套访问网络的规范,很明显,RealCall就是Call的实现类嘛。那么我们在最初访问网络的时候,调用了execute();也就是调用了接口的execute();也就是调用了实现类RealCall的execute();

我们有必要先去看看他的构造方法:

可以看到他传过来一个OkHttpClient对象和一个originalRequest(我们创建的Request)。把这两个实例的引用做了内存保存,调用他们的方法就轻而易举了。

分析到这里,我们也就能明白真正起作用的是这个RealCall类,看看他的execute();

首先加锁置标志位,表示高并发同时请求的时候只能执行一次。其次做了一个校验,这里也是3.6以后加入的,以前版本的okHttp不包含这个校验。因此我们不必太过于追求细节,追求整体流程。前两部完全可以忽略。往下,调用了OkhttpClient的disPatcher()获取到分配器DisPacter实例【我们在最初初始化OkhttpClient时候它在Builder里面已经做了初始化】,因此可以直接追进他的execute();即可

它表示使用分配器的executed方法将call加入到同步队列中,它是一个双向的队列,以后有机会再做分析。然后调用getResponseWithInterceptorChain方法(这个稍微复杂,稍后分析)执行http请求,并返回请求结果,如果请求完毕或者失败都会最终都会走finishied方法,它表示将call从同步队列中删除,他还是调用的分配器DisPacter的方法:finished(this);

在它里面把传入的call从同步队列中删除。

然后这里总结起来共做了五件事:

  1. 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。

  2. 做更多的校验工作(老版本的ojhttp不存在该校验)

  3. 利用 client.dispatcher().executed(this) 来进行实际执行,dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一,它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。

  4. 调用 getResponseWithInterceptorChain() 构建了一个拦截器链,该函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。

  5. 最后还要通知 dispatcher 自己已经执行完毕,表示将call在dispactcher里面移除掉。

dispatcher 这里我们不过度关注,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它我们的执行状态,比如开始执行了添加队列(调用 executed),比如执行完毕了移除队列(调用 finished),在异步执行流程中它会有更多的参与。

其实我们已经找到了根源,就是第四步,它返回了一个结果,这不就是我们Activity最终拿到的结果吗?

也就是说,这个方法封装了访问网络一切的核心,通过依次执行该拦截器链中的每一个拦截器最终得到服务器返回。

  • 在配置 OkHttpClient 时设置的 interceptors

  • 负责失败重试以及重定向的 RetryAndFollowUpInterceptor

  • 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor

  • 负责读取缓存直接返回、更新缓存的 CacheInterceptor

  • 负责和服务器建立连接的 ConnectInterceptor

  • 配置 OkHttpClient 时设置的 networkInterceptors

  • 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor

  • return chain.proceed(originalRequest);中开启链式调用:

其逻辑大致分为两部分:

  • 创建一系列拦截器,并将其放入一个拦截器数组(集合)中。这部分拦截器即包括用户自定义的拦截器也包括框架内部拦截器

  • 创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法

那么,我们在集合中添加了这么多的拦截器,是怎么遍历的呢?是通过foreh还是普通for?其实都不是:它是在RealInterceptorChain中做的处理。

在 OkHttp 开发者之一介绍 OkHttp 的文章里面,作者讲到:

the whole thing is just a stack of built-in interceptors.

可见 Interceptor 是 OkHttp 最核心的一个东西,不要误以为它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。

接下来看下RealInterceptorChain的实现逻辑:

在这里面进行了赋值操作。

然后再回到调用拦截器的proceed 方法的地方,点击去发现是一个接口类型

,很明显,上面已经标注,这里的RealInterceptorChain是实现了该接口,也就是调用RealInterceptorChain 拦截器的proceed 方法。

然后看一下这类里面的proceed 方法:


proceed方法中的核心代码可以看到,这里算是做了一个“遍历”。

总结起来proceed实际上也做了两件事:

  • new自己实例,创建下一个拦截链。传入index + 1使得下一个拦截器链只能从下一个拦截器开始访问

  • 执行索引为index的intercept方法,并将下一个拦截器链传入该方法

从 getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:

对于把 Request 变成 Response 这件事来说,每个 Interceptor 都可能完成这件事,所以我们循着链条让每个 Interceptor 自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个 Interceptor)。这样一来,完成网络请求这件事就彻底从 RealCall 类中剥离了出来,简化了各自的责任和逻辑。

接下来具体看一下拦截器类里面的实现intercept方法

看下第一个拦截器RetryAndFollowUpInterceptor的intercept方法:

该方法里面有一句核心方法:

这行代码就是执行下一个拦截器链的proceed方法。而我们知道在下一个拦截器链中又会执行下一个拦截器的intercept方法。所以整个执行链就在拦截器与拦截器链中交替执行,最终完成所有拦截器的操作。这也是OkHttp拦截器的链式执行逻辑。而一个拦截器的intercept方法所执行的逻辑大致分为三部分:

  • 在发起请求前对request进行处理

  • 调用下一个拦截器,获取response

  • 对response进行处理,返回给上一个拦截器

这就是OkHttp拦截器机制的核心逻辑。所以一个网络请求实际上就是一个个拦截器执行其intercept方法的过程。而这其中除了用户自定义的拦截器外还有几个核心拦截器完成了网络访问的核心逻辑,按照先后顺序依次是:

  • RetryAndFollowUpInterceptor

  • BridgeInterceptor

  • CacheInterceptor

  • ConnectIntercetot

  • CallServerInterceptor

RetryAndFollowUpInterceptor

如上文代码所示,RetryAndFollowUpInterceptor负责两部分逻辑:

  • 在网络请求失败后进行重试

  • 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接

BridgeInterceptor

BridgeInterceptor主要负责以下几部分内容:

  • 设置内容长度,内容编码

  • 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦

  • 添加cookie

  • 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤

CacheInterceptor

CacheInterceptor的职责很明确,就是负责Cache的管理

  • 当网络请求有符合要求的Cache时直接返回Cache

  • 当服务器返回内容有改变时更新当前cache

  • 如果当前cache失效,删除

ConnectInterceptor

建立连接

实际上建立连接就是创建了一个 HttpCodec 对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操作的抽象,有两个实现:Http1Codec 和 Http2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

在 Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装,Okio 它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。

ConnectInterceptor的intercept方法还有一行关键代码:

RealConnection connection = streamAllocation.connection();

即为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。

CallServerInterceptor

发送和接收数据


@Override public Response intercept(Chain chain) throws IOException {  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();  Request request = chain.request();  long sentRequestMillis = System.currentTimeMillis();  httpCodec.writeRequestHeaders(request);//向服务器发送 request header;流的方式  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {//如果有 request body,就向服务器发送;流的方式    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);    request.body().writeTo(bufferedRequestBody);    bufferedRequestBody.close();  }  httpCodec.finishRequest();//停止请求//读取 response header,先构造一个 Response 对象;  Response response = httpCodec.readResponseHeaders()      .request(request)      .handshake(streamAllocation.connection().handshake())      .sentRequestAtMillis(sentRequestMillis)      .receivedResponseAtMillis(System.currentTimeMillis())      .build();  if (!forWebSocket || response.code() != 101) {    response = response.newBuilder()//如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象;        .body(httpCodec.openResponseBody(response))        .build();  }  if ("close".equalsIgnoreCase(response.request().header("Connection"))      || "close".equalsIgnoreCase(response.header("Connection"))) {    streamAllocation.noNewStreams();  }  // 省略部分检查代码  return response;//返回最终访问网络后的结果}

抓住主干部分:

  1. 向服务器发送 request header;

  2. 如果有 request body,就向服务器发送;

  3. 读取 response header,先构造一个 Response 对象;

  4. 如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象;


这里我们可以看到,核心工作都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上还是用的 Socket

CallServerInterceptor负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。

在这里,就把服务器端的数据得到,并返回给我们的额客户端。



然后再来分析一下异步get请求:

首先初始化是一样的。然后调用请求,我们会传入一个CallBack

还是直接看RealCall的enqueue方法:

它跟同步差不多,首先也是一些校验,核心代码在于最后一行。交给DisPacher调用它的enqueue方法RealCall.enqueue实际就是讲一个RealCall放入到任务队列中,等待合适的机会执行。从代码中可以看到最终RealCall被转化成一个AsyncCall并被放入到任务队列中,new AsyncCall():

它这里面最终调用了executorService().execute(call);

也就是线程池的execute方法,这里不就是执行一个任务吗,我们使用线程池也是如此操作的,也就是每一个任务都是在子线程中执行。它这里的线程池最大线程数设置成了整数最大值。

然后再回到DisPacher的enqueue方法

这里又有两个请求队列。

都是添加call,但是一个是表示请求中添加,一个表示准备中添加。而且在不同条件下添加,这里主要目的是因为做了限制,原因是他的线程池是把任务限制到了最大值,而不可能无限的添加任务,所以这里做了校验。

而且这两个值是可以自己设定的。

然后再往下分析应该是分析线程里面的的run方法,看是如何在子线程中执行的:

我们发现AsyncCall没有run方法,而他的父亲里面有一个final类型的run方法,里面辗转之后,有一个抽象的方法,让子类执行:

也就是发起网络的请求在AsyncCall的execute()里面了。

可以看到又是先通过拦截器整个流程去网络获取数据。然后做了一个校验:

retryAndFollowUpInterceptor.isCanceled()表示请求取消,则回调客户端失败的方法,否则回调成功的方法并把结果返回给客户端,以及cache捕获里面也是回调了失败方法。终于找到了在哪里把结果返回给我们客户端了吧。

到这里,源码加载流程已经解析完毕了。


在文章最后我们再来回顾一下完整的流程图:

图一:

图二:

  • OkHttpClient 实现 Call.Factory,负责为 Request 创建 Call

  • RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用 ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过 getResponseWithInterceptorChain() 函数实现;

  • getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能;


当然你也要记住,okhttp底层其实就是Socket编程。


完成本篇文章参考了大量资料:

1、https://yq.aliyun.com/articles/78104?spm=5176.100239.blogcont78102.13.8Az14g/

2、http://www.jianshu.com/p/2710ed1e6b48?spm=5176.100239.blogcont78105.17.fhKs84

3、http://www.jianshu.com/p/27c1554b7fee

4、https://blog.piasy.com/2016/07/11/Understand-OkHttp/

5、http://blog.csdn.net/mynameishuangshuai/article/details/51303446

6、OkHttp3.6文档。


原创粉丝点击