网络请求框架-OkHttp原理解析

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();
1111.png

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======

2222.png

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) {
      ......
    }
  }
339.png

okhttp 拦截器原理解析

拦截器采用责任链的设计默认,让请求者和处理者解耦,最终请求从前往后,响应从后往前。


558.png

6650.png

重试重定向拦截器 RetryAndFollowUpInterceptor

首先先判断用户是否取消了请求,如果没有取消,就把请求交个桥接拦截器。
在获得响应结果response的时候根据响应码,判断是否需要重试或者重定向,重试不限制次数,重定向最多20次,如果需要重试或者重定向,那么会再一次重新执行所有拦截器。
有如下几种情况不会重试:IO异常,线路异常,配置client实例时配置不允许重试,协议异常,证书异常等等。

619.png

判断是否允许重定向使用返回的response的code码决定的,或者在构建client是设置是否允许重定向 followRegirects(默认为true)
037.png

桥接拦截器 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()))
      }
0451.png

缓存拦截器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

11423.png
1352.png

连接拦截器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)
  }

1420.png

访问服务拦截器CallServerInterceptor

拿到上一个拦截器返回的请求,真正的与服务器进行通信,向服务器发送数据,解析读取响应的数据,返回给上一个拦截器。

okhttp 通信流程总结

999.png

okhttp 面试题总结

OkHttp 请求的整体流程

1.创建request =>OkHttpClient=>RealCall()
2.同步执行 ,分发器添加同步任务,执行拦截器,访问服务器,返回reponse,触发异步分发流程。
3.异步执行 ,封装任务= >AsyncCall ,实现runnable接口。添加任务到异步任务等待队列,执行分发任务,判断异步任务是否能加入正在执行的异步任务队列,满足两个条件
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
4.加入正在执行的异步任务队列,通过线程池执行任务,经过5个默认拦截器访问服务器,返回response,执行异步任务分发。

分发器是如何工作的

分发器工作 分为同步任务和异步任务两种
同步任务 就是把任务加入同步任务队列,加个标记,执行结束之后,触发异步任务的分发操作。
异步任务 先封装任务到asyncCall对象,实现了runnable接口。把任务加入等待执行队列,执行分发操作。
先遍历等待任务队列,判断是否符合加入正在运行的异步任务队列,要同时满足两个条件。
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
当满足条件后,从等待队列中删除任务,把任务加入正在执行的队列中,通过自定义的线程池,执行任务,任务执行结束后,再次执行分发操作。

拦截器是如何工作的

拦截器采用了责任链设计默认,让请求者和执行者解耦,请求者只需要将请求发给责任链即可,无需关心请求过程和细节。okHttp 默认有5个拦截器,重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,请求服务拦截器。工作细节参考上面拦截器原理分析部分


6650.png

应用拦截器和网络拦截器的区别

1.位置的关系,应用拦截器 放在责任链最顶端,网络拦截器放在责任链倒数第二的位置。所以应用拦截器 最先拦截,最后响应,网络拦截器 倒数第二拦截,第二响应。如果打印请求日志的情况,应用拦截器打印的是用户请求信息,经过重试重定向,桥接,缓存,链接 等拦截器的层层包装,网络拦截器打印的是实际请求的信息。
2.应用拦截器一定会被执行,网络拦截器不一定被执行。

okHttp 如何复用TCP连接

利用连接池,缓存所有的有效连接对象。
清理机制:垃圾连接
1.超过5分钟没有用过的链接
2.超过5个闲置链接后,从最久闲置的链接开始执行清理(LRU)

你可能感兴趣的:(网络请求框架-OkHttp原理解析)