Android : 关于 OkHttp

本文深入分析了 OkHttp 4.x 的内部实现机制,包括 Call 和 RealCall 的工作原理、同步和异步请求处理流程、Dispatcher 的队列管理方式以及线程池的配置与使用。

写在前面

OkHttp 内部实现上有了一些新的东西,在此做下整理。

内容

简述

这里使用的 OkHttp 的版本为:

implementation("com.squareup.okhttp3:okhttp:4.9.0")

借用从百度图片上找的一张描述 OkHttp 整体结构的图
在这里插入图片描述还有一段官方的示例代码:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

可以看到请求的时候需要构建一个 Request,然后传给 OKHttpClient 里的 Call,由它去发起请求,然后拿到 Response。

Call & RealCall

OkHttpClient执行 newCall()方法的时候,其实是获取了一个 Call接口的实现类 RealCall

// OkHttpClient.kt
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
// Call.kt
interface Call : Cloneable {
  fun request(): Request  
  @Throws(IOException::class)
  fun execute(): Response
  fun enqueue(responseCallback: Callback)
  fun cancel()
  fun isExecuted(): Boolean
  fun isCanceled(): Boolean
  fun timeout(): Timeout
  public override fun clone(): Call

  fun interface Factory {
    fun newCall(request: Request): Call
  }
}
// RealCall.kt
class RealCall(
  val client: OkHttpClient,
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
  ...
}

发起请求

使用 ReallCall发起请求的时候只能执行一次,在开始的时候会对状态做检查,如果想要再请求,可以通过ReallCallclone()方法,来发起请求。

同步

// RealCall.kt
override fun execute(): Response { 
    // 使用 CAS 检查是否重复调用发起请求
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }

接着会调用 OkHttpClient里的Dispatcherexecuted()方法,并把当前的RealCall传入进去,然后就开始走拦截器里真正的请求。

异步

异步请求同样也会进行是否重复请求的检查,然后调用分发器进行异步请求,这里的参数为 AsyncCall,是RealCall的内部类,因此可以直接获取到当前的RealCall

// RealCall.kt
  override fun enqueue(responseCallback: Callback) {
    // 使用 CAS 检查是否重复调用发起请求
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
    ...
    val call: RealCall
        get() = this@RealCall
        ...
  }

Dispatcher

在分发器里,它维护着三个队列:

// Dispatcher.kt
  // 准备开启异步的队列
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
  // 正在异步执行的队列
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  // 正在运行的同步队列
  private val runningSyncCalls = ArrayDeque<RealCall>()

这三个队列使用的是ArrayDeque,里面是一个循环数组,有头尾指针,当头尾指针重叠的时候,意味着满了,可以进行扩容的操作。使用ArrayDeque可以节省空间。

它不是一个线程安全的类,所以下面可以看到,相关的操作方法需要进行加锁(synchronized)的操作。

同步

RealCall加入到正在运行的同步队列里

  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

异步

AsyncCall加入到readyAsyncCalls里,然后从readyAsyncCalls遍历取出下一个,如果runningAsyncCalls的长度大于等于最大请求数(默认64),则退出循环,去开始执行已有的异步请求;如果这个异步请求的对同一个域名的请求数量大于等于同一域名最大请求数(默认 5),则跳过下面的步骤,继续下一次循环,直到少于同一域名最大请求数。

当符合上面的两个条件,此时就把 AsyncCallreadyAsyncCalls里移除,添加到runningAsyncCallsexecutableCalls里。

最后遍历executableCalls,把每个 AsyncCall放入到线程池里,开始执行。


  @get:Synchronized var maxRequests = 64
    @get:Synchronized var maxRequestsPerHost = 5
  
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

      // 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()
  }
  
  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        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
  }

线程池

在这个线程池里,我们可以看到核心线程数为 0,最大线程数为 Int.MAX_VALUE,并且用了SynchronousQueue

 @get:Synchronized
  @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!!
    }

这样的话意味着这个线程池不会一直有核心线程在运行,所有线程到时间了可以被回收。其次由于使用了SynchronousQueue,这是个没有容量的队列,任务加到线程池里,无法进入到阻塞队列里,会直接通过新建线程来处理它,可以得到最大的并发和最快的处理。

线程池执行每一个 AsyncCall

线程池开始运行,就会执行 AsyncCallrun()方法,因为它实现了Runnable接口,所以在线程池里就会调用这个方法。流程上跟同步执行的execute()方法一样,都是会进入拦截器里去做真正的请求。

// AsyncCall.kt
    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()
          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)
        }
      }
    }

请求的最后

不管这个同步或异步的请求是否成功,都会走到finally块,这里调用了分发器的 finish()方法。在这个方法里,就是把 Call 从它们相应的队列里移除,然后再继续查看是否还有准备的异步请求要处理。

/** Used by [AsyncCall.run] to signal completion. */
  internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

  /** Used by [Call.execute] to signal completion. */
  internal fun finished(call: RealCall) {
    finished(runningSyncCalls, call)
  }

  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    // 再走一遍 promoteAndExecute)
    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值