okHttp源码阅读

使用方法

okhttp基本使用方法:

// 1、创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2、创建Request对象
Request request = new Request.Builder()
.url(url)
.build();
//3、通过okHttpClient的newCall方法获得一个Call对象
Call call = okHttpClient.newCall(request);
//4、请求
//同步请求
Response response = call.execute();
//异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
 //子线程
}

@Override
public void onResponse(Call call, Response response) throws IOException {
  //子线程
    }
});

源码阅读

按照okhttp的使用方法流程来读源码,首先是构建一个okhttpClient对象。来看源码是怎样构建的。

构造方法里new了一个Builder来看这个Builder类。


构造函数里主要是初始化了一堆属性变量和一些需要的对象,主要留意Dispatcher请求分发器和ConnectionPool连接池这两个对象,比较重要。这里可以看出来okhttpClient采用了建造者模式,okhttpClient对象还可以通过okhttpClient.Builder().build()来创建,可以设置一些自定义的属性比如超时时间等。

build方法里实际上也是new了一个okhttpClient对象。接下来进行第二个步骤构建Request对象。


从Request源码可以看出他也是基于建造者模式,他的默认构造方法里就两行设置默认请求方式是GET,另外添加了一些请求头信息。通常我们会使用new Request.Builder().url().build()方法构建Request来传入我们自己url等一些参数。

接下来第三步就是通过okhttpClient对象的newCall方法创建一个Call对象。来看okhttpClient的newCall方法:

在这个方法中实际上调用的RealCall这个类的newRealCall方法,并把request传进去。于是在进入RealCall类查看newRealCall方法:

可以看到newRealCall方法里只是创建了一个RealCall对象返回和一个eventListener。

RealCall的构造函数中可以看到除了传入的三个参数外,还新初始化一个RetryAndFollowUpInterceptor这么一个重试重定向拦截器,这里涉及到Okhttp里的拦截器机制,先不管还是先记下有这么一个拦截器。至此为止,第三步结束,进入最后一个步骤通过call.execute()或者call.enqueue方法发送同步或者异步请求。先来看同步的execute方法。

首先看到在同步代码块中判断了executed是否为true,executed是用来标识这个Call对象是否执行过的,所以每个Call只能执行一次,否则就会抛出异常。接着调用client.dispatcher().executed(this)这个方法,这个dispatcher就是之前在okhttpClient构造函数里初始化的那个请求分发器,来看这个dispatcher。

首先看这个dispatcher类中有哪些成员变量。默认规定了最大并发线程数、每个主机最大请求数,一个线程池用来执行Call,一个准备运行的异步请求队列,一个正在运行的异步请求队列,一个正在运行的同步请求队列。紧接着回看到之前调用的executed方法代码:

executed方法很简单,就是把Call对象添加到正在运行的同步请求队列中。

再回到call.execute方法中,client.dispatcher().executed(this)结束后执行了getResponseWithInterceptorChain()这个方法,返回的是请求结果Response,这个方法里面是调用执行okhttp的拦截器链从而通过一个个拦截器执行完成组装请求所需参数、设置缓存策略等等最终完成请求返回结果数据,涉及到okhttp的拦截器机制,先暂时不管,简单地看成通过这个方法得到了返回的Response。最后在finally代码块中执行了client.dispatcher().finished(this);方法:

可以看到finished又调用重载方法,首先从同步运行队列中remove了这个call,然后因为promoteCalls为false,没有执行promoteCalls这个重新整理排序队列的方法,直接执行了runngCallsCount()方法,计算正在运行的请求总数。

至此同步请求流程执行结束。接下来看异步请求,调用call.enqueue方法:

这里可以看到和同步方法一样先判断是否执行过,然后调用client.dispatcher().enqueue(new AsyncCall(responseCallback));方法把传进来的callback传入一个AsyncCall对象。进入dispatcher的enqueue方法中:

方法中首先判断正在运行的异步请求队列是否达到最大请求数和每个主机的最大请求数,达到了就把call加入到准备队列中,否则加入运行队列并且交给消费者线程池处理。所以我们很容易想到AsyncCall实际上是个Runnable。先来看看这个线程池是怎样创建的:

在初始化Dispatcher时候可以传入一个线程池,如果没传,默认会new一个核心线程数为0,最大线程数为
Integer.MAX_VALUE,线程超时时间为60秒的一个线程池,也就是说没任务时线程池中不会存在空闲线程,当一个线程空闲超过60秒后就会被销毁,同时最大线程数虽然为Integer.MAX_VALUE但是由于运行队列限制了最大请求数默认为64个,所以也就不会因为一直创建新线程而导致内存紧张。

再来看AsyncCall的代码:

AsyncCall是RealCall的一个内部类他果然继承了一个叫NameRunnable的接口,在NameRunnable的run方法里调用了execute方法,而这个方法的实现又是在AysncCall中。

这里同样执行了getResponseWithInterceptorChain()这个方法获得Response,然后判断请求是否取消,取消回调onFailure,没取消回调onResponse方法。最后在finally代码块中依旧调用了dispatcher().finished(this)方法

看清楚这次我们传入的是AysncCall所以会走上面这个finished方法最终同样的从运行队列中移除当前call对象,这回因为promoteCalls是true会执行promoteCalls这个方法了。

promoteCalls方法里判断了异步准备队列无请求和异步运行队列请求满的情况,如果都不满足,就从准备队列中取出请求放入运行队列中交给线程池处理并从准备队列中移除。promoteCalls执行完后又重新计正在运行的请求总数。至此异步请求流程结束。
这是OkHttp的执行流程图:

自己画的一个OkHttp原理的简化结构图:

简单的来说就是Call通过Dispatcher调度加入对应队列,异步请求由消费者线程池从队列中取出执行,调用拦截器链获得响应结果返回,这么一个过程。

接下来来看之前一直忽略的getResponseWithInterceptorChain()方法,来看看Response究竟是怎么获得的,这个拦截器链到底是什么。

getResponseWithInterceptorChain方法里的代码也不是很多,创建了一个存放interceptor拦截器的List集
合,并往里添加了许多拦截器,包括在RealCall构造函数中创建retryAndFollowUpInterceptor拦截器,之后创建了一个RealInterceptorChain真正的拦截器链对象,把刚才的list传入,并且调用了proceed方法传入request。

可以看到proceed(request)又调用了一个四个参数的重载方法,撇开抛异常的代码主要看143行,这里同样再次创建了一个next拦截器链对象,不同的是传入的第五个参数为index+1,这个index是干什么的,我们看到在getResponseWithInterceptorChain方法中创建拦截器链传入的index是0,再看146行147行从传入的拦截器list中取出下表为index的拦截器并调用了他的intercept方法传入了新的拦截器链next返回了response,进入
retryAndFollowUpInterceptor的intercept方法:

其他的代码先不看,直接看126行,这里又调用了传进来拦截器链的proceed方法,至此就搞清楚了okhttp的拦截器机制的执行流程,在getResponseWithInterceptorChain方法中初始化初始的拦截器链,调用拦截器链的proceed方法,在proceed方法中创建新的拦截器链并把index+1传入,取出下标为index的拦截器执行其intercept方法传入新的拦截器链,而在intercept方法中又会再次调用新拦截器链的proceed方法,如此循环往复,直至所有拦截器执行完成,返回最终的response。

这张图介绍了okhttp中的拦截器分为应用拦截器和网络连接器,会看getResponseWithInterceptorChain方法:

可以看到拦截器List中除了添加client里的应用拦截器和网络拦截器之外,还有红框这五个拦截器,接着来看看这些个拦截器是做什么的?

RetryAndFollowUpInterceptor(重试重定向拦截器)

看名字就能猜到这个拦截器的功能,就是用来重连和重定向的,来看源码:

主要看intercept方法,111行创建了一个StreamAllocation对象,这个对象是用来建立执行Http请求的一些组件的,可以看到传参有连接池,根据request的url创建地址等。接下来就是一系列的判断,在一个while(true)一直执行的循环中判断请求是否被取消等如果一旦出现异常就release掉StreamAllocation对象。在153号拿到重定向的请求,主要看168行这里判断了最大的重连次数超出最大次数同样releaseStreamAllocation对象。这就是这个拦截器的主要执行逻辑。

BridgeInterceptor(桥接拦截器)

还是看intercept方法,这里先是拿到传入的Request,然后为这个Request添加一系列请求头信息,例如看73行,connection,默认设置是keep-Alive,所以okhttp默认是长连接的。接下来93行,熟悉的调用拦截器链的proceed方法通过下面的拦截器获得Response,100行开始判断这个请求是否支持gzip压缩,支持就转换成GzipSource这个类型。

CacheInterceptor(缓存拦截器)

53行先判断cache是否为空,这个cache是在创建CacheInterceptor对象是传进来的cache,也就是创建OkHttpClient是设置的Cache,我们可以在创建OkHttpClient时候设置具体的缓存路径。59行创建了一个缓存策略,后面根据这个缓存策略来决定缓存是否有效,67行如果缓存Response不为空,但缓存策略设置为无效,则close这个Response;72行根据缓存策略既禁用网络并且缓存无效,就返回一个code为504,body空的Response;接下来85行,如果禁用网络但是有缓存就直接返回缓存的Response,93行如果之前都无返回,继续调用下一个拦截器访问网络返回Response;之后再看102行如果策略允许使用缓存并且网络返回的ResponseCode等于304,说明服务器无变化,就直接使用缓存Response。123行调用网络返回的Response.newBuilder()方法构建一个可以返回的Response对象,之后129行判断httpHeaders里有没有响应体,并且缓存策略是可以被缓存的,就把这个新的Response写入缓存;135行又判断了这个Request的方法是否是有效的方法,否则就从缓存中移除这个Request。

ConnectInterceptor(网络连接拦截器)

ConnectInterceptor中只做了两件事,通过在重试重定向拦截器初始化的StreamAllocation对象调用它的newStream方法,获得了一个HttpCodec对象,调用connection方法获得一个真正的连接。然后就是调用proceed方法传入下一个拦截器执行。首先来看newStream方法:

在114行调用findHealthyConnection方法获得一个健康的连接,再通过这个Connection连接newCodec创建了HttpCodec对象并返回。进入findHealthyConnection方法:


看到在while循环中又调用了findConnection方法返回一个candidate连接,在140行判断successCount是否为0,正确就return返回,147行判断这个candidate是否是健康的不是健康的就调用noNewStream方法然后继续循环。

noNewStream方法里将这个Connection置为null并且关闭销毁socket资源。接下来看findConnection方法:

主要看第174行,如果可以复用connection就复用这个connection,186行如果不能复用就从连接池中获取一个新的connection,获取connection之后在257行调用connect方法进行网络连接,然后267行将连接put入连接池。

CallServerInterceptor(向服务器发送获取响应的拦截器)

CallServerInterceptor获得四个对象:拦截器链RealInterceptorChain、HttpCodec封装的流对象、之前用到的StreamAllocation对象和Request。在第50行代码,调用HttpCodec.writeRequestHeaders方法向socket里写入请求头信息,在72行接着向socket写入请求body信息,接下来84行调用finishRequest方法说明请求到此写入结束。接着88行通过httpCodec.readResponseHeaders方法获的响应的头信息,在117行通过对websocket和code进行构建一个response,接下来就是对返回码进行一些判断抛出异常,无异常就返回response。

至此OkHttp的拦截器的机制的调用流程就阅读完了。总结一下拦截器链的顺序和功能流程图:

Cache

在缓存拦截器中用到了Cache这个类,用来读写缓存。先来看一下在OkHttp怎样设置缓存:

OkHttpClient client = new OkHttpClient.Builder()  
            .cache(new Cache(new File(this.getExternalCacheDir(), "okhttpcache"), 10 * 1024 * 1024))  
            .build();

在创建OkHttpClient可以是new一个Cache类设置缓存的路径和大小。接下来就来看看这缓存Cache类:

Cache类里有一个internalCache接口的实现,而这个实现里的方法都是调用Cache里的对应方法。除此之外,在169行有一个DiskLruCache对象,说明OkHttp的缓存全是基于DiskLruCache这个类实现的,接下来来看他的put方法:

在put方法的232行看到对请求的方法做了个判断,对所有非GET请求方式的请求返回null,不予缓存;243行创建了一个Entry对象这个对象就是要缓存内容。

可以看到Entry里主要是请求的url、请求头、请求方法、响应码、响应头等。接着在246行,创建了DiskLruCache.Editor传入key,key是以请求url进行MD5加密后转十六进制存储。然后250行调用Entry.wrireTo方法写入缓存

writeTo方法里写入url、请求头、请求方法、响应码、响应头等。接下来看get方法:

get方法里首先同样是根据url调用key方法获得一个key,然后调用cache的get方法去缓存里获取一个snapshot,如果snapshot为null就返回null;不为空就用snapshot创建一个Entry对象再从Entry里获取到Response返回。

ConnectionPool

连接池(ConnectionPool)是用来管理维护整个okhttp里的网络连接Connection。它里面主要有这么几个成员变量和方法:

第一个是个线程池,看注释这个线程池是用于在后台线程清除过期闲置的连接。一个cleanupRunnable用来清理连接。一个connections用于connection的缓存队列。接下来看put方法:

put方法里先判断清理了一下闲置过期连接,然后就只是将connection加入缓存队列。cleanupRunnable的run方法里首先获得下一次清理的时间然后调用wait方法等待下一次的清理。

get方法中循环了connections队列,判断连接是否是一个合格的连接,如果是就又调用StreamAllocation的acquire方法:

acquire方法里首先是把传过来的这个connection赋给了StreamAllocation里的成员变量,然后又把这StreamAllocation的弱引用添加到这个connection中的allocations里。

allocations就是一个存放引用的List集合,这里就是为了根据connection里的allocations的长度判断当前这个连接对象持有的StreamAllocation的数量,是否超过设置的最大值。包括cleanup连接时候也是根据allocations中的引用是否为空,如果为空就说明没有地方引用这个对象了就该删除它。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值