OkHttp3源码分析一

一、OkHttp3流程图

二、源码整体架构

上图是OkHttp3的整体框架,大致可以分为以下几层:

  • Interface——接口层:接收用户的网络访问请求(同步请求/异步请求),发起实际的网络访问
  • Protocol——协议层:处理协议逻辑
  • Connection——连接层:管理网络连接,发送新的请求,接收服务器访问
  • Cache——缓存层:管理本地缓存
  • I/O——I/O层:实际数据读写实现
  • Inteceptor——拦截器层:拦截网络访问,插入拦截逻辑

2.1 Interface——接口层

接口层接收用户的网络访问请求(同步请求/异步请求),发起实际的网络访问

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

  • Call:Call是描述一个实际的访问请求,用户的每一个网络请求都是一个Call实例。Call本身只是一个接口,定义了Call的接口方法,实际执行过程中,OkHttp会为每一个请求创建一个RealCall,每一个RealCall内部有一个AsyncCall,AsyncCall是RealCall的一个内部类并且继承NamedRunnable,那么首先看NamedRunnable类是什么样的,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public abstract class NamedRunnable implements Runnable {
    ......

    @Override
    public final void run() {
    ......
    try {
    execute();
    }
    ......
    }

    protected abstract void execute();
    }

    NamedRunnable实现了Runnable接口并且是一个抽象类,有一个抽象方法execute(),这个方法在run()中被调用。这也就意味着NamedRunnable是一个线程,并且其子类应该实现execute方法。下面再看AsyncCall的实现:

    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
    final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
    }

    ......
    final class RealCall implements Call {
    @Override protected void execute() {
    boolean signalledCallback = false;
    try {
    Response response = getResponseWithInterceptorChain();
    if (retryAndFollowUpInterceptor.isCanceled()) {
    signalledCallback = true;
    responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {
    signalledCallback = true;
    responseCallback.onResponse(RealCall.this, response);
    }
    } catch (IOException e) {
    ......
    responseCallback.onFailure(RealCall.this, e);

    } finally {
    client.dispatcher().finished(this);
    }
    }

    AsyncCall继承NamedRunnable,实现了抽象方法execute(),所以每一个Call就是一个线程,而执行Call的过程就是执行其execute()方法的过程。

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

    针对异步请求,Dispatcher使用了两个Deque,一个保存准备执行的请求,一个保存正在执行的请求,为什么要用两个呢?因为Dispatcher默认支持最大的并发请求是64个,单个Host最多执行5个并发请求,如果超过,则Call会先被放入到readyAsyncCall中,当出现空闲的线程时,再将readyAsyncCall中的线程移入到runningAsynCalls中,执行请求。

    如果正在执行的请求总数<=64 && 单个Host正在执行的请求<=5,则将请求加入到runningAsyncCalls集合中,紧接着就是利用线程池执行该请求,否则就将该请求放入readyAsyncCalls集合中。

2.2 Protocol——协议层

Protocol层负责处理协议逻辑,OkHttp支持Http1/Http2/WebSocket协议,并在3.7版本中放弃了对Spdy协议,鼓励开发者使用Http/2。
在早期的版本中,OkHttp支持Http1.0,1.1,SPDY协议,但是Http2协议的问世,导致OkHttp也做出了改变,OkHttp鼓励开发者使用HTTP2,不再对SPDY协议给予支持。另外,新版本的OkHttp还有一个新的亮点就是支持WebScoket,这样我们就可以非常方便的建立长连接了。在安全方便,OkHttp目前支持了TLS版本,以确保一个安全的Socket连接。

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

连接层顾名思义就是负责网络连接。在连接层中有一个连接池,统一管理所有的Socket连接,当用户新发起一个网络请求时,OkHttp会首先从连接池中查找是否有符合要求的连接,如果有则直接通过该连接发送网络请求;否则新创建一个网络连接。
虽然你只需要提供URL,但OkHttp计划它连接到您的网络服务器需要使用三种类型:URL,地址和路线.

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

2.4 Cache——缓存层

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

缓存策略:提到缓存策略,就要提到CacheInterceptor拦截器,如下图

CacheStrategy实现缓存策略,CacheStrategy使用Factory模式进行构造,该类决定是使用缓存还是使用网络请求

Cache是封装了实际的缓存操作;
DiskLruCache:Cache基于DiskLruCache;

2.5 I/O层

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

2.6 Inteceptor——拦截器层

拦截器是一种强大的机制,可以监视、重写和重试调用。拦截器层提供了一个类AOP接口,方便用户可以切入到各个层面对网络访问进行拦截并执行相关逻辑。

三、为什么okHttp3 好用呢?

OkHttp是一个精巧的网络请求库,有如下特性:

  • 支持http2,对一台机器的所有请求共享同一个socket
  • 内置连接池,支持连接复用,减少延迟
  • 支持透明的gzip压缩响应体
  • 通过缓存避免重复的请求
  • 请求失败时自动重试主机的其他ip,自动重定向
  • 好用的API

四、实现网络请求方法:

OkHttp3的最底层是Socket,而不是URLConnection,它通过Platform的Class.forName()反射获得当前Runtime使用的socket库

socket发起网络请求的流程一般是:
(1). 创建socket对象;
(2). 连接到目标网络;
(3). 进行输入输出流操作。

(1)(2)的实现,封装在connection接口中,具体的实现类是RealConnection。
(3)是通过stream接口来实现,根据不同的网络协议,有Http1xStream和Http2xStream两个实现类
由于创建网络连接的时间较久(如果是HTTP的话,需要进行三次握手),而请求经常是频繁的碎片化的,所以为了提高网络连接的效率,OKHttp3实现了网络连接复用

五、运用到的设计模式:

单例模式:(建议用单例模式创建okHttpClient)OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。 这一点很多同学,甚至在我经历的团队中就有人踩过坑, 每一个请求都创建一个 OkHttpClient 导致内存爆掉

*外观模式 : *OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块,全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口)

Builder模式 : OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象)

工厂方法模式:Call接口提供了内部接口Factory(用于将对象的创建延迟到该工厂类的子类中进行,从而实现动态的配置,工厂方法模式。(工厂方法模式:这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。)

享元模式:在Dispatcher的线程池中,所用到了享元模式,一个不限容量的线程池 , 线程空闲时存活时间为 60 秒。线程池实现了对象复用,降低线程创建开销,从设计模式上来讲,使用了享元模式。(享元模式:尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象,主要用于减少创建对象的数量,以减少内存占用和提高性能)

责任链模式:很明显,在okhttp中的拦截器模块,执行过程用到。OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试、请求对象转换、缓存、链接、网络读写(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。)

策略模式 :CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略(锦囊), CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。
缓存的策略过程:
1、 请求头包含 “If-Modified-Since” 或 “If-None-Match” 暂时不走缓存
2、 客户端通过 cacheControl 指定了无缓存,不走缓存
3、客户端通过 cacheControl 指定了缓存,则看缓存过期时间,符合要求走缓存。

4、 如果走了网络请求,响应状态码为 304(只有客户端请求头包含 “If-Modified-Since” 或 “If-None-Match” ,服务器数据没变化的话会返回304状态码,不会返回响应内容), 表示客户端继续用缓存。

(策略模式:一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。)

六、源码中用到的几个重要的类及作用解释:

OkhttpClient :

对外的API,OKHttp的很多功能模块,全部包装进这个类;创建分为两种:一种是new OkHttpClient()的方式,另一种是使用建造者(Builder)模式 – new OkHttpClient.Builder()….Build()。那么这两种方式有什么区别呢?

第一种:new OkHttpClient(),OkHttp做了很多工作,很多我们需要的参数在这里都获得默认值,也就是默认值设定。

第二种:默认的设置和第一种方式相同,但是我们可以利用建造者模式单独的设置每一个属性;
注意事项:OkHttpClient强烈建议全局单例使用,因为每一个OkHttpClient都有自己单独的连接池和线程池,复用连接池和线程池能够减少延迟、节省内存。

RealCall类:

集成Call类,从源代码中,可看到使用Call类,发送出(同步/异步)请求.RealCall的主要作用:发送请求,当中还有拦截器的建立过程,异步回调。

Dispatcher类(调度器,多线程):

保存同步和异步Call的地方,并负责执行异步AsyncCall

拦截器链:

有用户自定义的interceptor、retryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、 networkInterceptors、CallServerInterceptor。依次通过以上拦截器,传递给RealCall中的ApplicationInterceptorChain。拦截器之所以可以依次调用,并最终再从后先前返回Response,都依赖于ApplicatiionInterceptorChain的proceed方法.

HttpEngine类:

OKhttp底层的实现

坚持原创技术分享,您的支持将鼓励我继续创作!