okhttp——任务模型

简介

okhttp是Android中应用最广的http网络请求框架。结构优雅,性能强大。我们通过阅读它,对网络库的架构进行学习。本篇主要阅读okhttp任务队列的结构,了解okhttp的任务调度。

基本结构

网络请求是一个典型的生产/消费模型。我们的每一个请求都会加入一个缓冲队列,然后在合适的时候进行消费。

我们以异步请求为例:

基本架构

OKHttpClient提供方法生产Call,Dispatcher负责管理、分发和执行AsyncCall

下面,我们来细致地看一下代码。

详细代码

基本调用

还是以异步任务为例,一次典型的okhttp的调用:

String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
        .url(url)
        .get()//默认就是GET请求,可以不写
        .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: ");
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});

我们可以看到,这个过程大致分为两部,生成一个新的call,将call加入队列。

Request

Request是okhttp中的http请求封装类,它管理了http协议中的参数,如Header,RequestBody等

class Request internal constructor(
  internal val url: HttpUrl,
  builder: Builder
) {
  internal val method: String = builder.method
  internal val headers: Headers = builder.headers.build()
  internal val body: RequestBody? = builder.body
  internal val tags: Map<Class<*>, Any> = Util.immutableMap(builder.tags)

  ......

}

OKHttpClient

OKHttpClient是okhttp中总管类,管理了okhttp几乎所有抽象模块和各种配置。


open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
  private val dispatcher: Dispatcher = builder.dispatcher
  private val proxy: Proxy? = builder.proxy
  private val protocols: List<Protocol> = builder.protocols
  private val connectionSpecs: List<ConnectionSpec> = builder.connectionSpecs
  private val interceptors: List<Interceptor> =
      Util.immutableList(builder.interceptors)
  private val networkInterceptors: List<Interceptor> =
      Util.immutableList(builder.networkInterceptors)
  private val eventListenerFactory: EventListener.Factory = builder.eventListenerFactory
  private val proxySelector: ProxySelector = builder.proxySelector
  private val cookieJar: CookieJar = builder.cookieJar
  private val cache: Cache? = builder.cache
  private val socketFactory: SocketFactory = builder.socketFactory
  private val sslSocketFactory: SSLSocketFactory?
  private val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier
  private val certificatePinner: CertificatePinner
  private val proxyAuthenticator: Authenticator = builder.proxyAuthenticator
  private val authenticator: Authenticator = builder.authenticator
  private val connectionPool: ConnectionPool = builder.connectionPool
  private val dns: Dns = builder.dns
  private val followSslRedirects: Boolean = builder.followSslRedirects
  private val followRedirects: Boolean = builder.followRedirects
  private val retryOnConnectionFailure: Boolean = builder.retryOnConnectionFailure
  private val callTimeout: Int = builder.callTimeout
  private val connectTimeout: Int = builder.connectTimeout
  private val readTimeout: Int = builder.readTimeout
  private val writeTimeout: Int = builder.writeTimeout
  private val pingInterval: Int = builder.pingInterval
  private val internalCache: InternalCache? = builder.internalCache
  private val certificateChainCleaner: CertificateChainCleaner?
  ......

  /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call {
    return RealCall.newRealCall(this, request, false /* for web socket */)
  }

}

RealCall

RealCall是okhttp中任务的封装类

  companion object {
    fun newRealCall(
      client: OkHttpClient,
      originalRequest: Request,
      forWebSocket: Boolean
    ): RealCall {
      // Safely publish the Call instance to the EventListener.
      return RealCall(client, originalRequest, forWebSocket).apply {
        transmitter = Transmitter(client, this)
      }
    }
  }

internal class RealCall private constructor(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers.  */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
  /**
   * There is a cycle between the [Call] and [Transmitter] that makes this awkward.
   * This is set after immediately after creating the call instance.
   */
  private lateinit var transmitter: Transmitter

  // Guarded by this.
  var executed: Boolean = false

  @Synchronized override fun isExecuted(): Boolean = executed

  override fun isCanceled(): Boolean = transmitter.isCanceled

  override fun request(): Request = originalRequest

  override fun execute(): Response {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.timeoutEnter()
    transmitter.callStart()
    try {
      client.dispatcher().executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher().finished(this)
    }
  }

  override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.callStart()
    client.dispatcher().enqueue(AsyncCall(responseCallback))
  }

  override fun cancel() {
    transmitter.cancel()
  }
...
}

在异步任务的例子中,我们通常使用enqueue将我们的请求加入请求队列。
client.dispatcher().enqueue(AsyncCall(responseCallback))
这句话中,我们可以看到,我们新建了AsyncCall加入到OKHttpClient的dispatcher的队列中。

我们先看AsyncCall封装了什么

  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : NamedRunnable("OkHttp %s", redactedUrl()) {
    @Volatile private var callsPerHost = AtomicInteger(0)

    fun callsPerHost(): AtomicInteger = callsPerHost

    fun reuseCallsPerHostFrom(other: AsyncCall) {
      this.callsPerHost = other.callsPerHost
    }

    fun host(): String = originalRequest.url().host()

    fun request(): Request = originalRequest

    fun get(): RealCall = this@RealCall

    /**
     * Attempt to enqueue this async call on `executorService`. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    fun executeOn(executorService: ExecutorService) {
      assert(!Thread.holdsLock(client.dispatcher()))
      var success = false
      try {
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        transmitter.noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher().finished(this) // This call is no longer running!
        }
      }
    }

    override fun execute() {
      var signalledCallback = false
      transmitter.timeoutEnter()
      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(INFO, "Callback failure for ${toLoggableString()}", e)
        } else {
          responseCallback.onFailure(this@RealCall, e)
        }
      } finally {
        client.dispatcher().finished(this)
      }
    }
  }

我们可以看到,AsyncCall主要提供了executeOnexecute两个方法。

executeOn

executeOnAsyncCall自身的方法。它的主要内容是对异常的处理。如果一切正常,就用传入的ExecutorService执行当前AsyncCall

execute

execute是实现的Runnable的run方法。

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

我们可以看到,这个方法,只是为了给线程起名。
AsyncCall在线程池中执行时,execute方法就会被调用。execute方法的主要逻辑是:
getResponseWithInterceptorChain,它的实现,我们再不展开,属于okhttp网络能力了。

铺垫了上面这么多,我们终于可以看看okhttp的任务队列如何设计的了。

Dispatcher

  private var idleCallback: Runnable? = null

  /** Executes calls. Created lazily.  */
  private var executorService: ExecutorService? = null

  /** Ready async calls in the order they'll be run.  */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet.  */
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  /** Running synchronous calls. Includes canceled calls that haven't finished yet.  */
  private val runningSyncCalls = ArrayDeque<RealCall>()

  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.get().forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host())
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

这一段主要是AsyncCall的callsPerHost复用逻辑,注释说明比较清晰。如果有相同的AsyncCall存在于runningAsyncCalls中,则会增加callsPerHost。接下来,看一下promoteAndExecute的实现:

  private fun promoteAndExecute(): Boolean {
    assert(!Thread.holdsLock(this))

    val executableCalls = ArrayList<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
  }

promoteAndExecute方法,是将readyAsyncCalls队列中的任务,在最大任务数没有超标的情况下,移入runningAsyncCalls队列中。并对涉及转移的方法,调用executeOn方法。executeOn被调用后,就会执行到,RealCall中AsyncCall中的execute方法了。

小结

以上就是okhttp在请求和任务上的基本结构,还没有涉及到具体的网络请求。核心类是RealCallDispatcher,通过ArrayDequeAsyncCall完成任务的调度。

如有问题,欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容