OKHttp网络框架源码解析

OKHttp网络框架源码解析
一、OKHttp框架的特点
OKHttp是目前Android开发中最常用到的主流网络请求框架,它具有以下几个特点。
1、支持Http2和SPDY黑科技
2、Socket自动适配最优线路,拥有自动重连机制
3、对连接池进行了维护,减少了重新握手次数。
4、请求又队列进行管理,并通过定义的线程池来控制并发。
5、将请求和响应通过统一的接口规范进行定义,并且将请求的每个环节进行了细粒度,使源码分析更加容易,更易于扩展。
6、拥有强大的缓存策略。
7、支持同步和异步请求
二、使用OKHttp发起同步和异步请求
1、通过OKHttp发起同步请求
进行同步请求时,只需要执行三个步骤,便可以实现。
第一步:构建OkHttpClient
第二步:构建Request,并通过建造的形式,构建请求头数据。
第三步:通过OKHttpClient执行newCall方法,向该方法传入请求Request,最后执行execute方法即可实现同步请求。
通过OKHttp执行同步请求时,会对当前线程进行阻塞操作,当请求得到响应后,便会返回Response响应头实体,接下来就可以通过响应头Response来获取响应数据。关于OKHttp同步请求的实现,代码如下所示。
这里写图片描述
1、通过OKHttp发起异步请求
通过OKHttp执行异步请求时,前两个步骤与同步请求相同,只有第三步会有所不同。通过阅读以下代码可以看出,执行异步请求时,调用了enqueue方法。这时,OKHttp框架内部将会开启子线程来异步处理客户端的请求,当得到响应后,将会通过Callback将响应数据回传给客户端。阅读异步请求的代码就可以看出,在执行enqueue时,向异步请求中传递了回调Callback。
这里写图片描述
三、OKHttp同步请求源码分析
首先我们从第一步开始分析,在使用OKHttp发起网络请求时,需要首先构建出一个OkHttpClient客户端实例。
查看OkHttpClient源代码时会看到以下这段描述,大致意思是当你只创建一个OkHttpClient实例时,它的表现是最佳的,因为可以通过OkHttpClient这个实例,实现所有Http请求的调用,因为它内部维护了连接池和线程池,重用连接可以有效减少网络请求的延迟,并最大限度降低内存开销。相反,如果创建了多个OkHttpClient实例,则无法确保以上这些特性的发挥,同时也会造成资源的开销。
这里写图片描述
OkHttpClient类自身拥有很强的扩展性,通过阅读OkHttpClient的构造过程就可以很清晰的看出来。
OkHttpClient提供了一个普通的构造方法,同时还提供了一个通过建造者模式的Client构造过程。建造者模式在开源框架中应用非常多,该模式将复杂的对象构建与表示相分离,使同样的构建方式生成的同一种类型的对象,却拥有不同的表示。这样说的可能有些抽象,接下来通过分析这两个构建过程的方法就可以更加深入理解。
当调用端采用实例化的方式创建OkHttpClient时,可以看出,构造方法内部创建了一个默认的建造者实例Builder代码如下所示。
这里写图片描述
而这个实例对OkHttpClient中将会使用到的数据进行了默认的初始化操作,在初始化过程中,定义了OkHttpClient用到的socketFactory工厂实例、dispatcher分发器实例以及连接超时、读取超时的时长等,代码如下所示。
这里写图片描述
当然,这只是OkHttpClient在默认情况下的参数配置,当我们需要改变其中的某些配置,例如连接超时时长,socketFactory时,就可以通过第二种建造者方式来动态透明的指定,从而使OkHttpClient可以满足新的需求。
这样一来,建造者模式就实现了构建同一个OkHttpClient,但实际上的表示却有所不同,例如改变连接超时时长后,OkHttpClient实例在代码执行过程中的表示,将会根据建造者在构建时配置的时长为准,就不会再以默认情况下的配置为准了。
接下来分析newCall方法,通过观察源码可以看出,这个方法返回了一个RealCall实例,代码如下所示。
这里写图片描述
因此,具体实现需要进入RealCall去查看,在RealCall的构造方法中,对客户端Client、Request请求进行了记录。
这里写图片描述
由于同步请求是通过execute进行实现的,所以,接下来分析RealCall中的execute方法。通过阅读源码可以看出,首先进行了一个检查操作,检查这个同步请求是否被重复执行,如果当前被重复执行,则会抛出异常。
接下来通过分发器执行了executed方法,这时RealCall将会被放入到Dispatcher的同步请求队列中,接下来则通过getResponseWithInterceptorChain方法获取到了同步请求的响应,并进行返回。当同步请求执行完成后,将会通过分发器Dispatcher销毁这个同步请求,具体代码如下所示。
这里写图片描述
阅读上面的代码可以看出,getResponseWithInterceptorChain方法执行完成后返回了OkHttp网络请求的响应Response。因此,网络请求的处理过程,是由这个方法来完成的。
在OkHttp框架中,其实真正执行网络请求,并获取响应的地方,都是由getResponseWithInterceptorChain这个方法来完成的,该方法也是OkHttp的精髓所在,这个方法内部对网络请求中的每个环节都各自进行了接口的一致性封装,这个接口为拦截器,即Interceptor,每种不同的拦截器,他们都遵循了这个接口协议,但不同的拦截器,却处理各自不同的职责。例如ConnectInterceptor它的职责主要是根据请求Request,与服务器端建立连接;而BridgeInterceptor负责将客户端的请求,组织并转换为发送到服务端的请求;CacheInterceptor负责对缓存的访问和管理,并适时对缓存进行更新。
这样一来,OkHttp网络请求处理部分的核心骨架,就通过分层设计思想实现了在不同时期,执行不同的Interceptor,这也就将网络请求的每个复杂的环节,进行了一一拆分,经过拆分后,网络请求的每一个步骤,就变得更加简单且容易维护了,每一个环节就相当于每一个层次,也就是不同的Interceptor。最后,根据网络请求流程,依次执行不同的层次,并将这些不同层次的结果进行合并,再将合并结果传递给Response,这样一来,整个网络请求的处理过程,就由复杂变简单,再由每个简单环节的结果组合成复杂的响应,最终调用方就可以通过Response来获取网络请求的结果了。
这种设计思想是分层组合式架构设计思想,核心理念就是将复杂拆分为简单,再由简单组合出复杂。简单的地方是,由于采用了分层架构,这样每一层各司其职,只负责自身职责的实现,并通过定义的接口协议进行连接合并,最后形成完成的架构、功能体系。
getResponseWithInterceptorChain方法的源代码如下所示,通过阅读源码可以看出,OkHttp在进行网络请求时,将请求的每一个步骤,都细分成了不同的Interceptor。最后根据请求originalRequest以及这些不同的Interceptor,构建出组合执行链路Interceptor.Chain,从而实现完整的网络请求。
这里写图片描述
接下来将分别介绍几个Interceptor,这样就能更加深入地理解分层架构设计思想的作用。
ConnectInterceptor的作用主要是负责建立与服务器端的连接,由于intercept是Interceptor接口层定义的方法,因此,当执行该方法时,具体的Interceptor,将会各司其职。当ConnectInterceptor执行时,首先通过Interceptor.Chain获取到了请求Request。接下来通过StreamAllocation进行连接池操作。StreamAllocation类的职责主要负责连接池管理,它能最大限度地复用当前已有的连接池,关于StreamAllocation类的源码,后面再进行介绍,我们首先分析ConnectInterceptor的主要操作。
在ConnectInterceptor中,StreamAllocation执行了newStream这段代码,并得到了HttpCodec,接下来通过StreamAllocation又执行了connection方法得到了RealConnection,最后通过Interceptor.Chain,根据客户端请求Request,以及StreamAllocation、HttpCodec以及RealConnection,继续执行下一层的操作,代码如下所示。
这里写图片描述
通过阅读ConnectInterceptor依次执行的这些操作可以看出,首先StreamAllocation通过newStream操作获取HttpCode,这个过程是客户端与服务端进行握手的过程。
接下来我们首先分析Http请求,客户端与服务器端建立Http连接时,首先需要进行TCP握手操作,然后才能传输数据,最后将其关闭,流程图如下所示。
这里写图片描述
这个过程看似经历的步骤并不多,但是在移动端这种网络环境中,稳定性就不好保证了,特别是在移动端网络环境较差且复杂的网络环境中,创建Socket连接需要进行3次握手操作,而且在释放时还至少需要进行两次握手。这样就会导致很严重的网络延迟问题,因为我们不能拿移动设备的网络情况来与PC进行比较,当客户端与服务器端频繁进行这样的网络请求时,就会造成较大的网络延迟,从而影响用户体验。即时在网络好的情况下,如果不进行优化,频繁执行了一次相同的网络请求,也会经历重新握手等过程,也会产生一定的延迟。因此,针对客户端与服务器端进行Http请求时的握手阶段,需要采取相关的优化措施。
在Http请求中,有一种keepalive的机制,可以确保在第一次建立连接并传输数据后,在一定的时间段内,仍然保持着连接,当客户端再次进行该请求时,就可以直接对已有连接进行复用了,这样就减少了在不复用的情况下,重复请求产生的握手以及网络延迟了。
结合下面的流程图可以看出,当客户端第一次请求时,会依次经历流程图上的这些步骤,当客户端再次进行请求时,就不会去经历这些步骤了。
这里写图片描述
通过分析以上这些步骤,接下来将进入StreamAllocation源码进行分析,由于这个类在设计时,对连接池采用了复用机制,并通过keepalive机制,使连接池在一定的情况下,避免了重复请求产生的重复握手等过程。
由于ConnectInterceptor中通过StreamAllocation执行的newStream方法,使客户端发起了与服务端之间进行Http请求时的握手过程,因此,接下来我们分析newStream方法,看一看OkHttp框架在设计StreamAllocation时,是如何做到对连接池进行复用的。
在newStream方法中,可以看到这样的一行代码,执行了findHealthyConnection方法,接下来进入到这个方法进行分析。
这里写图片描述
首先我们通过阅读该方法的注释可以看出,这是一个获取连接的方法,它会首先查找当前是否存在可以直接复用的连接,如果已经存在,则会直接返回,做到对连接的复用。相反,如果当前不存在连接,或连接已经失效,则会进行重新创建。
在这个方法中,并没有对连接查找进行具体实现,而获取连接实例,又是通过这个方法内部调用的findConnection方法实现的,代码如下所示。因此,接下来我们继续跟进到这个方法中,来查看连接复用的过程具体是如何实现的。
这里写图片描述
在findConnection方法中,首先进行了一些安全检查策略,这里我们不关心这些。由于StreamAllocation是专门用于管理连接的,它还负责与连接池进行交互,从而最大限度地复用已经存在的连接。因此,当连接创建后,连接将会被直接记录到这个类中,并且会对连接池ConnectPool进行更新,以便于下次重用这个连接。
在findConnection方法内部,首先执行了以下这段代码,这段代码的含义就是首先检查当前StreamAllocation中是否已经存在可以直接使用的这个连接,如果能直接复用此连接,就通过return进行返回,就不会再继续向下执行了。
这里写图片描述
如果此时当前连接不可用,接下来就会尝试通过address等参数,从连接池中获取是否存在可以重用的连接。同理,若连接可用,将会返回给调用层,代码如下所示。
这里写图片描述
如果这时连接池中并不存在可以复用的连接,接下来就需要构建出连接。这里会首先routeSelector中获取路由配置,路由配置实际上就是对请求的ip地址等参数进行了一个统一的对象包装,这里我们不做对路由的深入分析,只要理解请求地址等参数,是通过路由进行包装的即可,以下这段代码就是获取路由的过程。
这里写图片描述
这时由于已经得到了路由,接下来就可以构建连接,并执行Http请求中的握手操作了,接下来会再次根据路由以及address地址,再一次尝试从连接池中获取连接,同理,存在的话就进行返回,由此可见,在StreamAllocation类的查找连接实例这个方法中,到目前为止,对连接池的访问已经达到了两次。
这里之所以会再次访问连接池,我个人的理解是,由于OkHttp支持同步和异步请求两种方式,在同步请求的情况下,这次对连接池的访问,获取到的结果不一定会有效,也就是说,如果都是同步请求,这次访问连接池,不会存在可复用的情况。但是,如果在异步请求时,这里对连接池的访问,那就会变得十分重要了。可以这样分析,这里之所以会再次访问连接池,有两个目的。第一,确保连接的重用性,减小网络延迟和握手操作;第二,这也是十分重要的,我们知道,OkHttp内部的Dispatcher内部维护了线程池,用于实现异步请求的并发操作,这里暂且先不分析Dispatcher,我们就以并发异步请求进行分析,我们知道,在Android系统中,主线程是不可以执行耗时操作的,虽然OkHttp提供了同步请求机制,但这种请求通常不会在主线程中直接使用,同步请求通常应用在客户端与服务器端进行一些账号级别的身份验证时使用,因为这个过程必须是同步的。因此,在Android应用的网络请求中,绝大部分网络请求,都将会是异步并发执行的,这样一来,就会涉及到一个线程安全问题,由于请求操作是异步且通过线程池来进行并发执行,我们可以模拟这样的场景,当客户端以最快的速度将一个请求执行两次甚至多次重复执行时,由于请求会被提交到线程池中,每个线程都会对这个连接池进行并发访问,就会带来一个情况,当请求A已经构建出连接并此时已经将连接进行池化了,但这时请求B开始获取连接(注:请求A和B所执行的请求完全相同),如果这时不访问这个连接池,代码执行到这一步时,请求B就会重新构建连接,虽然这两个请求是相同的,但此时并没有做到对已经存在的连接进行复用,这样一来就会产生一个性能问题,同时连接池在这时的连接复用机制,也就失效了。
因此,结合以上分析可以看出,在查找连接这个方法中,两次对连接池的访问操作,均是在以连接池为Lock锁的情况下,通过synchronized进行了同步代码块,在这个代码块当中进行的访问连接池操作。这样一来,就确保了在多任务并发的异步请求场景中,只要有请求相同的情况,并且其中一个请求已经创建了连接,那么,第二个请求在执行到这里时,就不会再重新创建了,就会直接通过连接池复用与其相同的请求对应的连接。这样一方面确保了高并发网络异步请求时,请求的高效性和低延迟,另一方面也确保了在这样的场景中,连接池中数据的安全性以及连接池的可靠性。
这里写图片描述
如果这时连接仍然没有,则会创建RealConnection,完成连接创建后,将会与服务器端进行TCP+TLS握手操作。通过RealConnection的connect方法实现,当完成握手操作后,会将连接记录到连接池中,并最终将连接返回给调用者,代码如下所示。
这里写图片描述
接下来我们继续回到StreamAllocation的newStream方法进行分析,由于这时已经得到了连接RealConnection,并且已经与服务器之间进行了握手和连接操作。因此,接下来将会得到服务端的响应HttpCode。在OkHttp框架中,对网络请求的编码和解码操作,都是通过HttpCode这个接口的具体实现来完成了,我们知道,在以往的开发中,通常会通过HttpUrlConnection来处理请求和响应,但是OkHttp框架则是通过OKio中的Sink和Source来实现的。其中,Sink相当于OutPutStream;Source相当于InputStream,这两者相比OutPutStream和InputStream更加强大。关于newStream方法的源代码,如下所示,可以看出,当得到RealConnection后,通过newCode方法得到了HttpCode。
这里写图片描述
目前,我们只分析了OkHttp框架拦截器中ConnectInterceptor,它主要负责连接的创建,其真正创建连接的过程,又是通过StreamAllocation实现的,而在StreamAllocation中,则通过对连接池的访问,做到了尽可能复用当前存活的连接,最后通过连接得到HttpCode响应。由此可见,从ConnectInterceptor的职责进行分析,它只负责连接的创建,并与服务端建立连接和进行握手操作。
四、OKHttp异步请求源码分析
OkHttp的异步请求操作,同样是通过RealCall发起的,通过RealCall中的enqueue方法,将异步请求提交到Dispatcher的线程池中执行,关于RealCall的enqueue方法,源代码如下所示。
这里写图片描述
在执行异步请求操作时,可以看出,OkHttp框架创建了一个AsyncCall,并将此请求提交到了Dispatcher类中,我们接下来首先分析AsyncCall的源代码。
AsyncCall实际上是对Runnable进行了一层封装,因此,当AsyncCall执行时,execute方法将会执行。通过我们分析同步请求时的getResponseWithInterceptorChain方法可以看出,该方法就是将Http网络请求的各个环境,进行了分层式架构封装,最终在该方法中,通过Interceptor.Chain锁链完成每个环节的Interceptor的拼接,最终形成了完整的网络请求操作。由此可见,在OkHttp的异步请求AsyncCall中,同样也是通过调用此方法,最终完成的网络请求,并根据Response请求结果,将其回调给具体的调用层。AsyncCall中的execute方法,源代码如下所示。
这里写图片描述
接下来我们分析Dispatcher中的源代码,由于在同步请求中,其实是牵扯不到Dispatcher的,但是,在执行异步请求操作时,AsyncCall就会被提交到Dispatcher的线程池中执行。
Dispatcher的enqueue方法是专门用于接收异步请求的方法,当AsyncCall被提交到该方法后,会首先检查当前任务队列是否可以接收并执行新的任务,如果当前情况允许,则会将AsyncCall直接提交到线程池中执行。相反,AsyncCall则会被放入到readyAsyncCalls队列中,直到空闲时再去执行,代码如下所示。
这里写图片描述
在Dispatcher中,负责异步任务的队列主要有两个,一个是runningAsyncCalls,另一个是readyAsyncCalls。其中,正在执行的任务AsyncCall,将会被放入到runningAsyncCalls队列中,而等待执行的任务,则会进入readyAsyncCalls。
这里写图片描述
关于OkHttp的Dispatcher中的线程池默认配置,源代码如下所示。除此之外,我们也可以自定义线程池,使Dispatcher可以满足业务需求。
这里写图片描述
目前仅对OkHttp框架的大致流程进行了分析,而请求重定向、缓存规则的处理、连接池的管理等源码部分,后续再一一进行分析。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值