首先理一下okHttp
使用的一般流程:
1:OkHttpClient.Builder okHttpClientBuilder=new OkHttpClient.Builder();//创建Builder,可以在这里设置OkHttpClient的相关参数,比如:new OkHttpClient.Builder().readTimeout(20, TimeUnit.SECONDS)
2:OkHttpClient client=okHttpClientBuilder.build();//Builder设计模式
3:Request request=new Request.Builder().url().build();//又是Builder设计模式,如果是post请求就是new Request.Builder().url().post(formBody).build()
4:Call call=client.newCall(request)//创建Call
//同步请求
5:Response response=call.excute();
//异步请求
6:call.enqueue(new CallBack(){
onFailure(Request request, IOException e) {
...
}
onResponse(Response response){
...
}
})
一步一步来看,1、2、3中是初始化的一些对象,设计到的点主要是Builder
的设计模式,在Builder
的构造方法中做了一些相关参数的默认初始化;
4中是通过client.newCall(request)
得到了一个Call
,其实client.newCall
中调用的是RealCall.newRealCall(request)
,newRealCall
是RealCall
的一个静态方法,RealCall.newRealCall
中new
了一个RealCall
对象并返回,同时newRealCall
还有一个OKHttpClient
类型的形参,这里调用的时候传入的是this
,即client
本身,所以此时new
出的这个RealCall
对象中是含有client
的引用的,所以我们看到的4中的Call call
实际上是一个RealCall
对象。
对同步请求来说,下一步调用的是call.excute()
,我们刚才说了这个call
实际上是一个RealCall
对象,所以call.excute()
实际上调用的是RealCall.excute()
方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
RealCall.excute()
方法中首先有一个用synchronized
加锁的代码块,该代码块通过一个布尔类型的excuted
标志位判断当前RealCall
的excute
是否已经被执行过,如果已经被执行过就抛出异常并将标志位赋值,这也就是为什么我们调用两次call.excute()
会抛异常的原因,如果当前RealCall
的excute
方法没有被执行过,则执行client.dispatcher().executed(this)
,而在dispatcher
的excute
中将当前RealCall
对象加入到了一个runningSyncCalls
的list
集合里面,然后调用Response result = getResponseWithInterceptorChain();
方法得到Response
,而在getResponseWithInterceptorChain()
方法中才是okHttp的精髓所在,getResponseWithInterceptorChain()
中首先会new
出一个Interceptor的集合(List)
,然后将我们调用addInterceptor()
添加的拦截器(在OkHttpclient.builder()
中添加)、retryAndFollowUpInterceptor
(负责失败重试以及重定向)、BridgeInterceptor
(请求时,对必要的Header
进行一些添加,接受响应时,移除必要的Header
)、CacheInterceptor
(负责读取缓存直接返回、更新缓存)、ConnectInterceptor
(负责和服务器建立连接)、我们通过addNetworkInterceptor()
添加的拦截器、CallServerInterceptor
( 负责向服务器发送请求数据、从服务器读取响应数据)拦截器依次加入该list
中,然后使用Interceptor.Chain chain = new RealInterceptorChain
,new
了一个RealInterceptorChain
,Interceptor.Chain
是一个接口,记做chain1
,传入了一个index=0
和我们刚才new
的Interceptors
集合,并执行了它的proceed(Request)
方法,RealInterceptorChain
是其实现类,而Response
也是由这个方法返回的,在RealInterceptorChain.proceed()
方法中,首先判断当前index
是否超过了Interceptor list
的大小,如果超过了就抛出异常,否则再new
一个RealInterceptorChain``记做chain2
,但是这次传入的参数是index 1
,当然还有刚才的Interceptor list
,然后根据Interceptor list
和当前index
获取到当前的Interceptor
,然后调用interceptor
的intercept()
方法,传入chain2``,在intercept
方法中又会调用chain2
的proceed
方法,这样一级一级将事件传递下去,最后到CallServerInterceptor
最终建立连接,最后,由于在RealCall.excute
方法中调用了client.dispatcher().executed(this)
将当前call
加入了dispater
的runningSynCalls list
中,所以在最后的try finally
块中会调用client.dispatcher().finished(this);
将之前加入的call
从runningSynCalls
中移除,interceptor
时最后必须返回chain.proceed(request
),否则会报错,这实际是一种责任链模式。
因为CallServerInterceptor
是最后一个拦截器,如果前面的拦截器都没有返回response
的话CallServerInterceptor.interceptor()
方法中是一定要返回response
的,所以在该方法中没有chain.proceed()
的调用,在该Interceptor
方法中我们可以看到,核心工作都由HttpCodec
对象完成,而 HttpCodec
实际上利用的是Okio
,而Okio
实际上还是用的 Socket
,所以没什么神秘的,只不过一层套一层,层数有点多。
RealInterceptorChain
的proceed
方法是一个被重载的方法,proceed(request)
会调用proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection)
,但是ConnectInterceptor
的intercept
方法会直接调用proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection)
而不是proceed(request
)。
异步我们一般调用的是client.enqueue(Callback)
,在RealCall.enqueue(Callback)
中也会首先用一个sychronized
代码块判断当前client
是否已经被enqueue
过,如果已经被执行过,则抛出异常,如果一切正常继续向下执行,其会调用client.dispatcher().enqueue(new AsyncCall(CallBack))
,这里使用CallBack
实例化了一个AsyncCall
并传入了enqueue
中,这个AsyncCall
一会再详细讲,在enqueue
中首先会将当前call add
到一个readyAsyncCalls list
中,然后调用promoteAndExecute()
方法,该方法中首先new``了一个AsyncCall
类型的list
,叫做executableCalls
,然后使用一个synchronized
块,在该synchronized
代码块中首先使用了一个循环对readyAsyncCalls
进行了遍历,对每一个readyAsyncCalls
中的RealCall
,首先判断当前runningAsyncCalls.size()
是否大于等于maxRequests
即最大的请求数量,如果大于,跳出循环,否则将当前RealCall
加入到runningAsyncCalls
和executableCalls
中,最后,对加入到executableCalls
中的每一个RealCall
对象,调用asyncCall.executeOn(executorService())
方法进行执行,executorService()
方法返回的是一个ExecutorService
类型的对象,在executorService()
里面new
了一个ThreadPoolExecutor
,corePoolSize
是0,maximumPoolSize
是Integer.MAX_VALUE
,并将其返回,所以说asyncCall.executeOn
中传入的是一个线程池,在executeOn
中使用传入的线程池执行了当前asyncCall``的run
方法,然后调用client.dispatcher().finished(this)
将当前RealCall
从runningAsyncCalls
中移除,而AsyncCall
是一个继承了NamedRunnable
抽象类的类,而NamedRunnable
实现了Runnable``接口,在NamedRunnable
中声明了一个抽象方法excute()
并在NamedRunnable
的run()``方法中调用了该excute()
,在AsyncCall
中实现了该excute
方法,在里面调用了getResponseWithInterceptorChain()
方法,之后根据不同的请求结果回调了我们传入的CallBack
对象的回调方法(onFailure
、onResponse
),之后就和同步请求是类似的逻辑了。
上面还提到了一个dispatcher
类,该类中维护了三个双端队列(Deque
):
readyAsyncCalls
:准备运行的异步请求runningAsyncCalls
:正在运行的异步请求runningSyncCalls
:正在运行的同步请求maxRequests
是最大同时请求数量, 大小是64.
至于那个excutorService()
涉及到的就是线程池的技术了。
addNetworkInterceptor
和addInterceptor
的区别:
关于这一点,主要的区别是调用次数的不一样,官方文档说如果url是重定向的,networkInrerceptor
会运行两次,第一次是request to"http://www.aaa.com",第二次是redirect to"http://aaa.com",如果有的api没有重定向这一说的话就只会调用一次networkInterceptor
,否则就是两次。
客户端和服务器建立socket
连接需要经历TCP
的三次握手和四次挥手,是一种比较消耗资源的动作。Http
中有一种keepAlive connections
的机制,在和客户端通信结束以后可以保持连接指定的时间。连接池的默认配置是5个空闲连接,每个空闲连接保持5分钟。当连接被占用时会和一个StreamAllocation``建立引用关系,所以可以从connections
这个队列里看出哪些连接是空闲的,那些连接是被占用的。
一个清理线程在后台工作,通过cleanup
方法实现清理,如果有一个连接满足被清理的条件(存活时间超时或空闲连接过多),就将其清理掉
okHttp
中的连接池逻辑主要是通过ConnectionPool
实现的,在ConnectionPool
的内部有一个private static final Executor executor =new ThreadPoolExecutor()
,其线程池基本大小是0,线程池最大大小是Integer.MAX_VALUE
,它是在Okhttpclient
的builder
中被创建的,的里的几个重要变量:
(1)executor
线程池,类似于CachedThreadPool
,用于执行清理空闲连接的任务。
(2)Deque
双向队列,同时具有队列和栈的性质,经常在缓存中被使用,里面维护的RealConnection
是socket
物理连接的包装
(3)RouteDatabase
,用来记录连接失败的路线名单
遍历连接池里的连接connections
,通过address
等条件找到到符合条件的连接,如果有符合条件的连接则复用。需要注意的是,这里还调用了streamAllocation
的acquire
方法。acquire
方法的作用是对RealConnection
引用的streamAllocation
进行计数,OkHttp3
是通过RealConnection
的StreamAllocation
的引用计数是否为0来实现自动回收连接的。
acquire计数加1
release计数减1
添加连接之前会先调用线程池执行清理空闲连接的任务,也就是回收空闲的连接。
cleanupRunnable
中执行清理任务是通过cleanup
方法来完成,cleanup
方法会返回下次需要清理的间隔时间,然后会调用wait
方法释放锁和时间片。等时间到了就再次进行清理。
通过每个连接的引用计数对象StreamAllocation
的计数来回收空闲的连接,向连接池添加新的连接时会触发执行清理空闲连接的任务。清理空闲连接的任务通过线程池来执行。