Android网络请求库【OkHttp4.9.3】基本用法与原理分析


OkHttp是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,Retrofit + OkHttp实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。

OkHttp的高效性体现在:

  • HTTP/2 允许对同一主机的所有请求共享一个套接字
  • 连接池可以降低请求延迟
  • GZIP压缩帮助开发者减小下载内容的体积
  • 响应缓存避免重复的网络请求

OkHttp简单使用

第一步:创建OkHttpClient,创建OkHttpClient有两种方式:

  1. 使用OkHttpClient()创建(使用默认配置)
  2. 使用OkHttpClient.Builder()构建(自定义配置信息)
val okHttpClient = OkHttpClient()
//OR
 val okHttpClient = OkHttpClient.Builder().build();

OkHttpClient提供了丰富的配置方法,例如添加拦截器、指定连接池、设置请求超时等等。

第二步:创建请求

使用Request.Builder() 构建Request实例

val request: Request = Request.Builder()
        .addHeader("token", "abcd")
        .get()
        .url("https://publicobject.com/helloworld.txt")
        .build()

第三步:发起网络请求

OkHttp支持同步和异步两种请求方式

  • 同步请求
executorService.execute {
    try {
        val response = okHttpClient.newCall(request).execute() //同步
        Log.d("response:", response.body!!.string())
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
  1. execute()方法需要运行在try-catch块中
  2. 网络请求是耗时操作,不能直接在主线程发起网络请求
  • 异步请求
okHttpClient.newCall(request).enqueue(object : Callback {
    //异步
    override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
    }

    @Throws(IOException::class)
    override fun onResponse(call: Call, response: Response) {
        Log.d("response:", response.body!!.string())
    }
})

OkHttp的使用方法非常简单,三步操作就可以发起一个简单的同步或异步请求。我们也可以很轻松地对网络请求进行配置,例如添加请求头、设置请求方式、设置请求超时等等,这些配置参数会在源码分析过程中详细介绍。

源码分析

现在我们已经学会了三步操作发起网络请求,接下来以这三个步骤为切入点,深入到源码中学习OkHttp的实现原理,废话少说马上开车。

OkHttpClient

OkHttpClient创建方式有两种,我们看看两种方式有什么区别。

第一种直接使用默认构造函数,内部依然是使用建造者模式

constructor() : this(Builder())  //this方法参数是默认的Builder

第二种使用建造者模式

fun build(): OkHttpClient = OkHttpClient(this)

两种方式最终都是调用构造函数OkHttpClient(builder:Builder),由参数builder负责所有的参数配置工作。

//构造函数OkHttpClient(builder:Builder)
open class OkHttpClient internal constructor(  
  builder: Builder    
) : Cloneable, Call.Factory, WebSocket.Factory {

  @get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher

  @get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool

  /**
   * Returns an immutable list of interceptors that observe the full span of each call: from before
   * the connection is established (if any) until after the response source is selected (either the
   * origin server, cache, or both).
   */
  @get:JvmName("interceptors") val interceptors: List =
      builder.interceptors.toImmutableList()

  ……

  constructor() : this(Builder())  //OkHttpClient 默认构造函数

  class Builder constructor() {  //Builder默认构造函数
    internal var dispatcher: Dispatcher = Dispatcher()
    internal var connectionPool: ConnectionPool = ConnectionPool()
    internal val interceptors: MutableList = mutableListOf()
    ……

    internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      this.connectionPool = okHttpClient.connectionPool
      this.interceptors += okHttpClient.interceptors
      ……
    }

    fun build(): OkHttpClient = OkHttpClient(this)
  }

}

当您创建单个OkHttpClient实例并将其用于所有 HTTP 调用时,OkHttp 性能最佳。 这是因为每个OkHttpClient都拥有自己的连接池和线程池,重用连接和线程可减少延迟并节省内存。 相反,为每个请求创建一个客户端会浪费空闲池上的资源。

Request

Request同样使用建造者模式来创建,这里贴上部分重要源码,很简单就不细说了。

  //部分代码
class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,
  @get:JvmName("method") val method: String,
  @get:JvmName("headers") val headers: Headers,
  @get:JvmName("body") val body: RequestBody?,
  internal val tags: Map, Any>
) {

  open class Builder {
    internal var url: HttpUrl? = null
    internal var method: String
    internal var headers: Headers.Builder
    internal var body: RequestBody? = null

    /** A mutable map of tags, or an immutable empty map if we don't have any. */
    internal var tags: MutableMap, Any> = mutableMapOf()

    constructor() {
      this.method = "GET"
      this.headers = Headers.Builder()
    }

    internal constructor(request: Request) {
      this.url = request.url
      this.method = request.method
      this.body = request.body
      this.tags = if (request.tags.isEmpty()) {
        mutableMapOf()
      } else {
        request.tags.toMutableMap()
      }
      this.headers = request.headers.newBuilder()
    }

    open fun url(url: HttpUrl): Builder = apply {
      this.url = url
    }

    open fun addHeader(name: String, value: String) = apply {
      headers.add(name, value)
    }

    open fun get() = method("GET", null)

    open fun head() = method("HEAD", null)

    open fun post(body: RequestBody) = method("POST", body)

    open fun method(method: String, body: RequestBody?): Builder = apply {
      require(method.isNotEmpty()) {
        "method.isEmpty() == true"
      }
      if (body == null) {
        require(!HttpMethod.requiresRequestBody(method)) {
          "method $method must have a request body."
        }
      } else {
        require(HttpMethod.permitsRequestBody(method)) {
          "method $method must not have a request body."
        }
      }
      this.method = method
      this.body = body
    }

    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }
  }

}

发起网络请求

OkHttp发起网络请求分为同步请求和异步请求两种方式,我们只分析异步请求流程,因为只要理解了异步请求过程,基本上也就明白同步请求是怎么一回事了。

//异步请求
okHttpClient.newCall(request)  //1.创建RealCall
  .enqueue(object : Callback {  //2.发起异步请求,通过接口回调请求结果
    //异步
    override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
    }

    @Throws(IOException::class)
    override fun onResponse(call: Call, response: Response) {
        Log.d("response:", response.body!!.string())
    }
})
  1. 首先在okHttpClient中根据request新建一个RealCall对象,这个Call才是网络请求的真正发起者。
  /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

RealCall是连接应用层与网络层的桥梁,负责处理连接、请求、响应和数据流。

  1. RealCall发起异步请求,通过接口回调这个请求的结果。
  override fun enqueue(responseCallback: Callback) {
    //首先检查请求任务是否已经执行
    check(executed.compareAndSet(false, true)) { "Already Executed" } 
    //请求开始前可以执行一些操作
    callStart()
    //分发器执行异步任务
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

Dispatcher维护着一套异步任务执行策略,分析策略之前先介绍几个重要概念:

  @get:Synchronized var maxRequests = 64  //并发执行的最大请求数

  @get:Synchronized var maxRequestsPerHost = 5  //单个主机并发执行的最大请求数

  @get:JvmName("executorService") val executorService: ExecutorService  //异步请求的线程池
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

  /** Ready async calls in the order they'll be run. */
//等待执行的异步请求队列
  private val readyAsyncCalls = ArrayDeque()  

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
 //正在执行的异步请求队列,包括被取消的请求
  private val runningAsyncCalls = ArrayDeque() 

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
 //正在执行的同步请求队列,包括被取消的请求
  private val runningSyncCalls = ArrayDeque()

client.dispatcher.enqueue(AsyncCall(responseCallback))执行步骤为:

  1. 异步请求包装成AsyncCall,AsyncCall是实现Runnable接口的异步任务
  2. 将异步任务交提交给dispatcher执行
//Dispatcher.java
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)   //1

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()   //2
  }
  1. 将任务添加到等待执行的异步请求队列readyAsyncCalls
  2. 执行promoteAndExecute()方法
  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()  //1
      while (i.hasNext()) {
        val asyncCall = i.next()
        //2
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
        //3
        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {  //4
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
  1. 遍历等待执行的异步请求队列,开始处理异步请求任务
  2. 如果正在执行的异步请求任务数 >= maxRequests,跳出while循环;如果每个主机上的异步请求任务数量 >= maxRequestsPerHost,进入下一次循环
  3. 把当前任务从readyAsyncCalls移除并且加入到队列executableCalls和runningAsyncCalls
  4. 遍历executableCalls任务队列,丢进线程池executorService执行
//AsyncCall.java
    fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        executorService.execute(this)  //1
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!  //2
        }
      }
    }

  /** 通知分发器[AsyncCall.run]处理完毕*/
  internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

  /** 通知分发器[Call.execute]处理完毕 */
  internal fun finished(call: RealCall) {
    finished(runningSyncCalls, call)
  }

  private fun  finished(calls: Deque, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      //从calls队列移除当前Call
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }
    //处理任务的过程中又会有新的任务加入到readyAsyncCalls,
    //所以这里要重新执行promoteAndExecute()方法
    val isRunning = promoteAndExecute()  
    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }
  1. 将AsyncCall加入到线程池中等待执行(AsyncCall实现了Runnable接口)
  2. 如果任务处理失败即success=false,调用client.dispatcher.finished(this)方法表示当前AsyncCall任务执行完毕

异步任务执行策略小结

1、将网络请求包装成异步任务AsyncCall,加入到等待执行队列readyAsyncCalls
2、遍历readyAsyncCalls,如果正在执行的异步请求任务数 >= maxRequests,跳出遍历循环;如果每个主机上的异步请求任务数量 >= maxRequestsPerHost,进入下一次遍历循环
3、通过第二步筛选,符合条件的任务加入到executableCalls
4、遍历executableCalls,取出任务丢进线程池中执行

AsyncCall实现了Runnable接口,因此一旦被线程池中的线程处理就会调用它的run()方法:

internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {

    ……

    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()  //1
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response) 
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)  
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException) 
          }
          throw t
        } finally {
          client.dispatcher.finished(this)  //2
        }
      }
    }
  }
}
  1. 拦截器责任链处理并获取网络请求结果。这是责任链模式的具体应用,是OkHttp框架的精髓所在
  2. 通知分发器:当前AsyncCall已经执行完毕,看看还有没有其他任务需要执行

话休絮烦,我们开始分析拦截器责任链:

//RealCall.java
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf()  //1
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

    //2
    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 {
      val response = chain.proceed(originalRequest)  //3
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }
  1. 将所有拦截器添加到interceptors 列表中,各种拦截器作用见下表


    拦截器作用表
  2. 构建拦截器责任链RealInterceptorChain
  3. 调用chain.proceed()开始处理责任链
//RealInterceptorChain.java
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++
    //如果责任链是处理network interceptor,则exchange != null
    if (exchange != null) {
      check(exchange.finder.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    //调用责任链上的下一个拦截器,next就是index+1后的责任链对象
    val next = copy(index = index + 1, request = request)
    //当前拦截器
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    //执行当前拦截器的intercept方法
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }

责任链执行流程:首先获取当前拦截器interceptor,并且调用interceptor.intercept(next)执行拦截器操作。这里的next表示的是index+1后的责任链对象,拦截器的intercept()方法内部会调用next.proceed(request)方法再次进入到责任链,由于此时index已经加1,所以处理的是下一个拦截器。

如此循环往复,直到处理完责任链上最后一个拦截器为止。

注意除最后一个拦截器CallServerInterceptor不会调用chain.proceed(request)方法之外,其他拦截器都应该至少调用一次chain.proceed(request)方法。

为了验证上面的结论,我们进入到RetryAndFollowUpInterceptor的intercept()方法一探究竟:

 //省略处理错误重试和重定向代码
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    var followUpCount = 0
    var priorResponse: Response? = null
    var newExchangeFinder = true
    var recoveredFailures = listOf()
    while (true) {
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)

      var response: Response
      var closeActiveExchange = true
      try {
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }
        try {
          response = realChain.proceed(request)  //1.重新进入责任链处理下一个拦截器
          newExchangeFinder = true
        } catch (e: RouteException) {
          // The attempt to connect via a route failed. The request will not have been sent.
          continue
        } catch (e: IOException) {
          // An attempt to communicate with a server failed. The request may have been sent.
          continue
        }
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

可以看到注释1处重新进入责任链处理下一个拦截器。

有兴趣可以自行查看最后一个拦截器CallServerInterceptor源码,此处只给出本人阅读源码后得出的结论:

  • CallServerInterceptor主要负责处理客户端发起的request请求数据和服务端返回的response响应数据
  • CallServerInterceptor#intercept()方法中没有调用chain.proceed(request)方法

以上就是拦截器责任链的工作流程,我们再通过流程图仔细感受一下。

拦截器责任链工作流程

分析完拦截器责任链,我们继续分析AsyncCall#run()方法:

internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {

    ……

    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()  //1
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response) 
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)  
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException) 
          }
          throw t
        } finally {
          client.dispatcher.finished(this) 
        }
      }
    }
  }
}

我们看到,如果getResponseWithInterceptorChain()方法成功获得服务端返回的数据,则调用responseCallback.onResponse(this@RealCall, response)方法完成异步回调;如果服务端数据获取失败(请求异常),则调用responseCallback.onFailure(this@RealCall, canceledException)方法完成异步回调

需要注意的是,responseCallback回调是在子线程中完成的,所以如果想把数据显示到UI上,需要切换回主线程进行UI操作。

总结

OkHttp发起网络请求全过程:

  1. 通过建造者模式构建OKHttpClient与Request
  2. OKHttpClient通过newCall发起一个新的请求
  3. 通过分发器维护请求队列与线程池,完成请求调配
  4. 通过五大默认拦截器完成请求重试,缓存处理,建立连接等一系列操作
  5. 得到网络请求结果
OkHttp发起网络请求全过程
参考资料

【知识点】OkHttp 原理 8 连问

你可能感兴趣的:(Android网络请求库【OkHttp4.9.3】基本用法与原理分析)