OKHttp理解
okhttp是square公司贡献的一个处理网络请求的开源框架,是目前Android开发使用最广泛的一个网络框架,从Android4.4开始,httpURLconnection的底层实现采用的就是okhttp。内部实现就是利用java基础,对socket进行封装,实现http通信。最重要的两个关键点就是分发器和5个拦截器。
分发器就是内部维护队列和线程池,完成请求分配,总结就是用于对异步任务加入队列管理,然后判断条件,控制数量,加入线程池执行异步请求任务。
五个默认拦截器就是利用责任链模式对网络请求进行层层处理,完成整个请求过程,简单总结如下。
1.桥接拦截器对用户发出的请求添加缺少的请求配置字段,比如keep-alive等
2.缓存拦截器就是查询有没有符合判断条件的已缓存的网络请求,执行复用,直接返回response
3.连接拦截器就是创建请求,加入连接器 或者访问连接池,根据条件判断,是否能怼已创建的tcp请求进行复用
4.请求服务器拦截器就是对scoket进行操作,请求网络访问服务器,返回response,
5.重试和重定向拦截器就是对返回的response进行code判断,决定是否要重试或者重定向操作。
okhttp的优点
1.支持http2.0版本,并且允许对同一主机的所有请求共享一个套接字
2.即使不是http2.0版本,通过连接池,减少请求延迟
3.默认使用Gzip 压缩数据
4.响应缓存,避免重复请求网络
okHttp的执行流程
最简单的http请求案例
val client = OkHttpClient.Builder().build()
val request= Request.Builder().url("https://www.baidu.com").build()
//异步请求
client.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
//同步请求
client.newCall(request).execute();
1.利用建造者模式构建okHttpClient实例对象,构建过程中可以动态配置参数,请求时间,响应时间,缓存信息等。
2.创建Request对象,设置请求方式,链接地址,参数等信息。
3.把request对象,传给client,通过newCall函数,得到RealCall对象。
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
4.RealCall 分为同步和异步执行
5.同步执行时,分发器只是做个记录,把请求任务加到队列中,然后直接通过拦截器访问服务器,返回response。
override fun execute(): Response {
....
try {
client.dispatcher.executed(this)//分发器记录任务 runningSyncCalls.add(call)
return getResponseWithInterceptorChain() //返回请求结果 response
} finally {
client.dispatcher.finished(this)
}
}
6.异步执行
6.1先对异步任务进一步封装,把任务放到AsyncCall对象中
override fun enqueue(responseCallback: Callback) {
...
client.dispatcher.enqueue(AsyncCall(responseCallback))
//AsyncCall(responseCallback)对异步任务进一步封装
}
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable
2.分发器 把 封装后的异步任务 添加到等待运行的队列中
======分发器维护的三个队列======
//等待运行的异步任务队列
private val readyAsyncCalls = ArrayDeque()
//正在运行的异步任务队列
private val runningAsyncCalls = ArrayDeque()
//同步任务队列
private val runningSyncCalls = ArrayDeque()
//异步任务,加入队列
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
if (!call.call.forWebSocket) {
//在等待队列或者运行队列中找出有没有相同host的请求任务
val existingCall = findExistingCallWithHost(call.host)
//如果有相同host的请求,那么就把他们的callsPerHost 设置为同一个
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//执行分发操作
promoteAndExecute()
}
=======syncCall 字段说明==================
//这个字段用来设置 当前 正在执行的任务,有多少个是相同host
@Volatile var callsPerHost = AtomicInteger(0)
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
========end=======================
//分发操作
private fun promoteAndExecute(): Boolean {
//临时容器
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
//遍历等待运行的异步任务队列
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//正在执行的异步任务数量 不能大于默认最大数量64
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 同一个时刻,对同一个host的访问数量,不能大于5个
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
//删除等待队列的异步任务
i.remove()
//设置任务主机Host 的数量
asyncCall.callsPerHost.incrementAndGet()
//把任务加入临时容器
executableCalls.add(asyncCall)
//把任务加入正在运行的队列
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//利用线程池,执行异步任务
asyncCall.executeOn(executorService)
}
return isRunning
}
====线程池的配置======
okHttp 配置的线程池,等同于jdk提供的newCacheThreadPool
核心线程的数量为0,最大线程数量不限制,阻塞队列不能存储元素。
这样配置的好处就是可以并发执行多个异步任务。如果阻塞队列能添加元素,比如LinkedBlockingQueue 只有等上个任务结束之后,才能执行下个任务。
val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
=======end=======
//执行异步任务
fun executeOn(executorService: ExecutorService) {
....
try {
executorService.execute(this) //执行syncCall的run函数
success = true
} catch (e: RejectedExecutionException) {
.....
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
//执行子线程任务
override fun run() {
......
try {
//利用拦截器,请求网络,分发器任务结束
val response = getResponseWithInterceptorChain()
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
......
} finally {
client.dispatcher.finished(this)
}
}
}
=====同步或者异步,最终都会调用 client.dispatcher.finished(this)======
internal fun finished(call: RealCall) {//同步任务结束
finished(runningSyncCalls, call)
}
internal fun finished(call: AsyncCall) {//异步任务结束
call.callsPerHost.decrementAndGet() //对相同主机数量 进行-1操作
finished(runningAsyncCalls, call)
}
private fun finished(calls: Deque, call: T) {
....
val isRunning = promoteAndExecute() //再次去分发异步任务
....
}
=======end======
7.getResponseWithInterceptorChain 通过拦截器,获取response
okhttp 默认提供5个拦截器 重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,访问服务器拦截器。还可以自定义拦截器。
自定义拦截器分为应用拦截器(通过addInterceptor 添加)和网络拦截器(通过addNetworkInterceptor拦截)
=====添加自定义拦截器=========
val client = OkHttpClient.Builder()
.addInterceptor (object :Interceptor{ //添加应用拦截器
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest = request.newBuilder().addHeader("token","11111111").build()
return chain.proceed(newRequest)
}
})
.addNetworkInterceptor(object :Interceptor{//添加网络拦截器
override fun intercept(chain: Interceptor.Chain): Response {
省略一些拦截操作 xxxxxxxx
return chain.proceed(chain.request())
}
})
.build()
=========end========
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf()
interceptors += client.interceptors //添加应用拦截器
interceptors += RetryAndFollowUpInterceptor(client) //重试重定向
interceptors += BridgeInterceptor(client.cookieJar) //桥接
interceptors += CacheInterceptor(client.cache) //缓存拦截器
interceptors += ConnectInterceptor //连接拦截器
if (!forWebSocket) {
interceptors += client.networkInterceptors //网络拦截器
}
interceptors += CallServerInterceptor(forWebSocket) //访问服务拦截器
//构建 链接对象
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
//经过拦截器 响应请求,返回response
val response = chain.proceed(originalRequest)
....
return response
} catch (e: IOException) {
......
}
}
okhttp 拦截器原理解析
拦截器采用责任链的设计默认,让请求者和处理者解耦,最终请求从前往后,响应从后往前。
重试重定向拦截器 RetryAndFollowUpInterceptor
首先先判断用户是否取消了请求,如果没有取消,就把请求交个桥接拦截器。
在获得响应结果response的时候根据响应码,判断是否需要重试或者重定向,重试不限制次数,重定向最多20次,如果需要重试或者重定向,那么会再一次重新执行所有拦截器。
有如下几种情况不会重试:IO异常,线路异常,配置client实例时配置不允许重试,协议异常,证书异常等等。
判断是否允许重定向使用返回的response的code码决定的,或者在构建client是设置是否允许重定向 followRegirects(默认为true)
桥接拦截器 BridgeInterceptor
先获取用户发送的请求,判断条件用户是否已经配置过请求头字段,若用户没有配置,则将http协议必备的请求头字段补齐,比如Content-Type,Content-Length等,然后交给下一个拦截器。
在获得响应结果response之后,调用保存cookie的接口(也可以在配置client的时候,设置cookjar进行cookie回调数据),并且解析gzip数据
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
获取结果之后,对cookie进行保存,对返回的数据进行gzip解压
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
缓存拦截器CacheInterceptor
就是根据缓存策略从缓存中查找是否有合适的缓存response,如果有合适的缓存,直接返回给请求任务,不在继续执行后面的拦截器。
获得响应结果response后,根据条件判断,决定是否要缓存。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
连接拦截器ConnectInterceptor
维护一个连接池,负责对连接的服务。在把请求交给下一个拦截器之前。会先在连接池中找到一个合适的连接(满足适配条件相同,并且没有正在被使用)或者新建一个连接,并且接入连接池,获得对应的socket流,把请求交给下一个拦截器。获得response结果后不会进行额外的处理。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)//入口处
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
internal fun initExchange(chain: RealInterceptorChain): Exchange {
....
val exchangeFinder = this.exchangeFinder!!
val codec = exchangeFinder.find(client, chain) //查找连接池
val result = Exchange(this, eventListener, exchangeFinder, codec)
....
}
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
val resultConnection = findHealthyConnection(
....
)
return resultConnection.newCodec(client, chain)
} ...
}
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
val candidate = findConnection(... )//携带信息,查找连接池.
.....
}
======RealConnection 判断复用条件========================
internal fun isEligible(address: Address, routes: List?): Boolean {
assertThreadHoldsLock()
// 判断这个连接是否正在被使用
if (calls.size >= allocationLimit || noNewExchanges) return false
//判断基础信息是否相同 证书,协议,url,ssl等
if (!this.route.address.equalsNonHost(address)) return false
// 请的host也必须一直
if (address.url.host == this.route().address.url.host) {
return true // This connection is a perfect match.
}
===必须跟下面的信息相同的请求才能复用 this.route.address.equalsNonHost(address)=================
internal fun equalsNonHost(that: Address): Boolean {
return this.dns == that.dns &&
this.proxyAuthenticator == that.proxyAuthenticator &&
this.protocols == that.protocols &&
this.connectionSpecs == that.connectionSpecs &&
this.proxySelector == that.proxySelector &&
this.proxy == that.proxy &&
this.sslSocketFactory == that.sslSocketFactory &&
this.hostnameVerifier == that.hostnameVerifier &&
this.certificatePinner == that.certificatePinner &&
this.url.port == that.url.port
}
连接池,也称之为对象池,主要用来存放request请求连接,内部维护了一个LinkedQueue队列用来存放请求。在添加新的请求对象时,都会执行一个周期性任务,用以对连接池进行清理操作。
1.队列长度超过5,清理最近未被使用连接,LRE算法
2.存储的连接,5分钟未被复用,清理
====RealConnectionPool 实例化====
class ConnectionPool internal constructor(
internal val delegate: RealConnectionPool
) {
constructor(
maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit
) : this(RealConnectionPool(//this(5, 5, TimeUnit.MINUTES)
taskRunner = TaskRunner.INSTANCE,
maxIdleConnections = maxIdleConnections,
keepAliveDuration = keepAliveDuration,
timeUnit = timeUnit
))
constructor() : this(5, 5, TimeUnit.MINUTES)
======end===========
class RealConnectionPool(
taskRunner: TaskRunner,
/** The maximum number of idle connections for each address. */
private val maxIdleConnections: Int, //队列的长度-- 5
keepAliveDuration: Long, //休闲请求的存活时间 ---5
timeUnit: TimeUnit//单位 TimeUnit.MINUTES
) {
//周期性的清理任务
private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
override fun runOnce() = cleanup(System.nanoTime())
1.队列长度超过5,清理最近未被使用request LRE算法
2.空闲请求,5分钟未被复用,清理
}
//队列保存请求对象
private val connections = ConcurrentLinkedQueue()
//保存请求对象,开启周期性任务
fun put(connection: RealConnection) {
connection.assertThreadHoldsLock()
connections.add(connection)
cleanupQueue.schedule(cleanupTask)
}
访问服务拦截器CallServerInterceptor
拿到上一个拦截器返回的请求,真正的与服务器进行通信,向服务器发送数据,解析读取响应的数据,返回给上一个拦截器。
okhttp 通信流程总结
okhttp 面试题总结
OkHttp 请求的整体流程
1.创建request =>OkHttpClient=>RealCall()
2.同步执行 ,分发器添加同步任务,执行拦截器,访问服务器,返回reponse,触发异步分发流程。
3.异步执行 ,封装任务= >AsyncCall ,实现runnable接口。添加任务到异步任务等待队列,执行分发任务,判断异步任务是否能加入正在执行的异步任务队列,满足两个条件
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
4.加入正在执行的异步任务队列,通过线程池执行任务,经过5个默认拦截器访问服务器,返回response,执行异步任务分发。
分发器是如何工作的
分发器工作 分为同步任务和异步任务两种
同步任务 就是把任务加入同步任务队列,加个标记,执行结束之后,触发异步任务的分发操作。
异步任务 先封装任务到asyncCall对象,实现了runnable接口。把任务加入等待执行队列,执行分发操作。
先遍历等待任务队列,判断是否符合加入正在运行的异步任务队列,要同时满足两个条件。
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
当满足条件后,从等待队列中删除任务,把任务加入正在执行的队列中,通过自定义的线程池,执行任务,任务执行结束后,再次执行分发操作。
拦截器是如何工作的
拦截器采用了责任链设计默认,让请求者和执行者解耦,请求者只需要将请求发给责任链即可,无需关心请求过程和细节。okHttp 默认有5个拦截器,重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,请求服务拦截器。工作细节参考上面拦截器原理分析部分
应用拦截器和网络拦截器的区别
1.位置的关系,应用拦截器 放在责任链最顶端,网络拦截器放在责任链倒数第二的位置。所以应用拦截器 最先拦截,最后响应,网络拦截器 倒数第二拦截,第二响应。如果打印请求日志的情况,应用拦截器打印的是用户请求信息,经过重试重定向,桥接,缓存,链接 等拦截器的层层包装,网络拦截器打印的是实际请求的信息。
2.应用拦截器一定会被执行,网络拦截器不一定被执行。
okHttp 如何复用TCP连接
利用连接池,缓存所有的有效连接对象。
清理机制:垃圾连接
1.超过5分钟没有用过的链接
2.超过5个闲置链接后,从最久闲置的链接开始执行清理(LRU)