okhttp源码阅读(二) --->同步(execute)、异步(enqueue)主流程源码分析

每日一道面试题

问:什么是反射机制?反射机制的应用场景有哪些?

答:在运行状态中,对于任意一个类,都能够获取这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 应用场景:

1、逆向代码,例如反编译

2、与注解相结合的框架,如 Retrofit

3、单纯的反射机制应用框架,例如 EventBus(事件总线)

4、动态生成类框架 例如Gson

前言

1、okhttp怎样使用呢?

2、它的原理到底是怎样的?

3、由于篇章问题,本章不讲解拦截器的源码,这部分留到下一篇章讲解

正文

使用方式

流程图

伪代码

1、创建okHttp对象

2、构建Request请求对象

3、生成call对象

4、call发起请求(同步/异步)

代码

kotlin 复制代码
fun createAndEnqueue(){
    val urlString = ""
    val okhttpClient = OkHttpClient()
    val request = Request.Builder().url(urlString).get().build()
    val call = okhttpClient.newCall(request)
    //同步执行
    Thread(object :Runnable{
        override fun run() {
            val response = call.execute()
            val bodyString = response.body?.toString()
        }

    }).start()
    //异步执行
    call.enqueue(object:Callback{
        override fun onFailure(call: Call, e: IOException) {
            //请求失败
        }

        override fun onResponse(call: Call, response: Response) {
            //请求成功
            val body = response.body?.string()
        }

    })
}

在createAndEnqueue方法中,我们使用了okhttp的同步(execute)、异步(enqueue)方法进行网络请求,并获取了网络请求返回的结果(body)。细心的读者可能注意到,okhttp的同步(execute)和异步(enqueue)请求使用方式有点不同,为什么同步(exeute)方法需要放到子线程执行,而异步(enqueue)方法却不需要? 其实它们的命名已经给了我们答案。同步(execute)请求是在当前线程执行并返回结果 ;在android中,当前线程是主线程,而主线程又负责着界面的绘制等等流程,如果我们在主线程执行的话,那么就有可能导致主线程阻塞,直接导致app发生了ANR(应用无响应)。异步(enqueue)请求是在线程池中执行并通过回调返回结果,不会造成当前线程的阻塞。

源码

okhttpClient类

kotlin 复制代码
open class OkHttpClient internal constructor(
    builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

    @get:JvmName("dispatcher")
    val dispatcher: Dispatcher = builder.dispatcher
    ...
    //省略部分代码
    constructor() : this(Builder())
    ...
    //省略部分代码
    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
    ...
    //省略部分代码
    class Builder constructor() {
        internal var dispatcher: Dispatcher = Dispatcher()
        internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
        ...//省略部分代码

        internal constructor(okHttpClient: OkHttpClient) : this() {
            this.dispatcher = okHttpClient.dispatcher
            this.eventListenerFactory = okHttpClient.eventListenerFactory
            ...//省略部分代码
        }
        ...//省略部分代码
        /**
         * Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
         */
        fun dispatcher(dispatcher: Dispatcher) = apply {
            this.dispatcher = dispatcher
        }
        ...//省略部分代码
    }
}

在OkHttpClient源码中可以看到,okHttpClient构造方法传入的是okhttp里的Builder对象,而Builder对象的构造方法主要是初始化属性。调用okHttpClient的newClall方法生成的是RealCall对象(请侧重注意这个RealCall对象,后面的同步/异步执行都是在这个对象里

Request类

kotlin 复制代码
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<Class<*>, Any>
) {
    ...//省略部分源码
    fun newBuilder(): Builder = Builder(this)
    ...//省略部分源码
    open class Builder {
        internal var url: HttpUrl? = null
        internal var method: String
        internal var headers: Headers.Builder
        internal var body: RequestBody? = null
        ...//省略部分属性

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

        internal constructor(request: okhttp3.Request) {
            this.url = request.url
            this.method = request.method
            this.body = request.body
            ...//省略部分属性初始化
            this.headers = request.headers.newBuilder()
        }
        ...//省略部分代码
        open fun build(): okhttp3.Request {
            return Request(
                checkNotNull(url) { "url == null" },
                method,
                headers.build(),
                body,
                tags.toImmutableMap()
            )
        }
    }
}

在Request源码中可以看到,调用newBuilder方法获取的是Request的Bulider对象,属性初始化都在Builder执行;在Bulider的默认构造方法可以看到Request默认使用的是Get请求。Builder对象的build方法返回的是Request对象。简单来说,就是初始化一个Request对象。

RealCall类

timeout、cancel方法

kotlin 复制代码
private val timeout = object : AsyncTimeout() {
  override fun timedOut() {
     //取消当前请求的执行
    cancel()
  }
}.apply {
  timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
}

//取消
override fun cancel() {
  if (canceled) return // Already canceled.
  //标志该执行已经取消
  canceled = true
  exchange?.cancel()
  //在连接拦截器ConnectInterceptor-->realChain.call.initExchange()-->exchangeFinder.find()-->findHealthyConnection()-->findConnection()-->val newConnection =RealConnection(connectionPool, route) call.connectionToCancel = newConnection
  connectionToCancel?.cancel()
  //监听回调
  eventListener.canceled(this)
}

enqueue(异步执行)

流程图

enqueue源码

kotlin 复制代码
override fun enqueue(responseCallback: Callback) {
  //检查当前是否请求是否正在执行
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  callStart()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

在源码中可以看到,异步(enqueue)请求方法源码非常简单,只有3句。那么它的具体逻辑是怎样的呢?首先执行的canllStart()方法,我们进入callStart()源码一探究竟

Dispatcher-->callStart()

kotlin 复制代码
private fun callStart() {
初始化callStackTrace属性
  this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
  //eventLitener -->client.eventListenerFactory.create(this)-->EventListener.NONE.asFactory()
  eventListener.callStart(this)
}

在callstart()方法,它主要的作用是初始化callStackTrace属性和调用eventListener的callStart()方法。那么eventListener是什么?在RealCall类中,internal val eventListener:EventListener = client.eventListenerFactory.create(this)可以看到eventListener其实是client(OkHttpClient)中的eventListenerFactory属性。我们继续一步一步追踪发现,eventListenerFactory其实是在OkHttpClient的Builder类中被赋值的,它的初始值是EventListener.NONE.asFactory(),是一个空实现类;当然我们在构建OkHttpClient对象可以传入自己的EventListener来监听请求执行到了哪个步骤。(eventListener赋值链:RealCall:eventLitener -->OkHttpClient:eventListenerFactory-->Builder:eventListenerFactory=EventListener.NONE.asFactory())

我们继续在enqueue方法里往下看client.dispatcher.enqueue(AsyncCall(responseCallback)), 在这段代码中,它调用了client(OkHttpClient)的dispatcher属性的enqueue方法。而OkHttpClient的dispatcher属性是获取OkHttpClient中Builer的dispatcher属性。在Buidler对象,dispather属性的默认值是Dispatcher()对象;当然我们也可以通过构建OkHttpClient对象传入自定义的dispather属性值,但是一般情况下我们使用框架默认的Dispatcher对象即可。

因此client.dispatcher.enqueue(AsyncCall(responseCallback))其实调用的是Dispathcher类中的enqueue方法。具体逻辑是如何的呢?让我们进入源码一探究竟

Dispatcher-->enqueue()

kotlin 复制代码
internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)
    //判断forWebSocket是为false(不是网页请求)
    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()
}

在enqueue方法的同步代码块中,我们可以看到call(AsyncCall)添加到了readyAsyncCalls集合中,那么readyAsyncCalls到底是什么集合?在Dispatcher属性声明的地方,可以看到除了readyAsyncCalls还有runningAsyncCalls、runningSyncCalls,它们到底是什么?作用是什么?

kotlin 复制代码
/** 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>()

通过属性声明和注释,我们可知readyAsyncCalls(等待队列)、runningAsyncCalls(正在执行请求的异步队列)、runningSyncCalls(正在执行请求的同步队列)都是双端队列。(什么是双端队列?为什么okHttp使用双端队列?请查看okHttp源码阅读(一)--->双端队列(ArrayDeque)源码分析 - 掘金 (juejin.cn) 它们的作用是:

readyAsyncCalls(等待队列):将需要等待的请求加入到此队列中

runningAsyncCalls(正在执行请求的异步队列):将正在异步执行的请求加入到此队列

runningSyncCalls(正在执行请求的同步队列):将正在同步执行的请求加入到此队列

了解readyAsyncCalls、runningAsyncCalls、runningSyncCalls之后。我们继续往下看

findExistingCallWithHost()

kotlin 复制代码
private fun findExistingCallWithHost(host: String): AsyncCall? {
  for (existingCall in runningAsyncCalls) {
    if (existingCall.host == host) return existingCall
  }
  for (existingCall in readyAsyncCalls) {
    if (existingCall.host == host) return existingCall
  }
  return null
}

在findExistingCallWithHost方法中,首先遍历了异步执行队列runningAsyncCalls,判断执行队列中是否有这个域名(host)的元素(AsyncCall),如果有,则返回。否,则遍历等待队列readyAsyncCalls,判断等待队列中是否有这个域名(host)的元素(AsyncCall),如果有,则返回。否,则返回null。if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)如果返回的元素不为null,则进入RealCall的reuseCallsPerHostFrom方法,这个方法的主要作用是将上一步找到相同域名的RealCall对象中的callsPerHost属性值赋值给当前执行的AsyncCall的callsPerHost。那么callsPerHos到底是什么,它的作用是什么?我们继续往后看 (在进入promoteAndExecute方法之前我先需要了解线程池的概念和线程池各个参数的意义)

线程池

线程池:由一组线程组成的线程队列。线程池里的线程可被复用,从而避免需要频繁创建线程来执行需要异步执行的各个任务。线程池的使用降低了线程创建和销毁的开销,提高了系统的性能。

js 复制代码
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去

maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量

keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁

unit:keepAliveTime的单位

workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种

threadFactory:线程工厂,用于创建线程,一般用默认即可

handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务

promoteAndExecute()

kotlin 复制代码
private fun promoteAndExecute(): Boolean {
  //判断当前显示已经被锁住
  this.assertThreadDoesntHoldLock()
  //声明数组executableCalls
  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    //获取双端队列的iterator对象
    val i = readyAsyncCalls.iterator()
    //遍历等待队列
    while (i.hasNext()) {
        //获取等待队列的元素
      val asyncCall = i.next()
      //maxRequests=64 判断当前所有正在执行的异步请求是否大于等于64,是则跳出遍历
      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      //callsPerHost:当前所有执行请求的同一个host请求数量 
      //maxRequestsPerHost = 5判断所有正在执行请求的桶一个域名请求是否大于等于5
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
      //等待队列移除该元素
      i.remove()
      //callsPerHost+1
      asyncCall.callsPerHost.incrementAndGet()
      executableCalls.add(asyncCall)
      //添加到异步执行队列中
      runningAsyncCalls.add(asyncCall)
    }
    //runningCallsCount(): Int = runningAsyncCalls.size + runningSyncCalls.size
    //判断当前执行队列(异步执行队列和同步执行队列的和)是否大于0
    isRunning = runningCallsCount() > 0
  }
  //遍历队列(可以执行的所有请求)
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
   //使用线程池执行请求
    asyncCall.executeOn(executorService)
  }

  return isRunning
}

//线程池
@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!!
  }

promoteAndExecute方法的具体解析请看上面的注释;它主要的作用是判断当前请求是否可以执行,判断条件是:

1、正在执行的异步请求数是否小于等于64

2、正在执行异步队列中和这个请求相同域名的请求数是否小于等于5

executorService是一个核心线程数为0,最大线程数为Int的最大值,线程等待存活时间为60秒,任务队列为SynchronousQueue的线程池。线程池的工作原理如下图所示:

SynchronousQueue是一个无容量阻塞队列。从线程池的工作原理可以得出线程池executorService这样创建的好处是:任务可以及时运行,无需等待。

promoteAndExecute()方法里的asyncCall.executeOn(executorService),它调用的是AsyncCall类中的executeOn方法,executorService是Dispatcher类中所创建的一个无需等待即可执行任务的线程池。

AsyncCall

kotlin 复制代码
internal inner class AsyncCall(private val responseCallback: Callback) : Runnable {
  @Volatile var callsPerHost = AtomicInteger(0)
  //记录当前执行请求中相同域名(host)请求数量
    private set
  fun reuseCallsPerHostFrom(other: AsyncCall) {
    this.callsPerHost = other.callsPerHost
  }
  
  //获取域名
  val host: String
    get() = originalRequest.url.host
  //获取Request对象
  val request: Request
      get() = originalRequest
  //获取RealCall对象
  val call: RealCall
      get() = this@RealCall
      
   //任务执行
  fun executeOn(executorService: ExecutorService) {
  //判断当前线程是否被锁住
    client.dispatcher.assertThreadDoesntHoldLock()

    var success = false
    try {
      //线程池执行该任务AsyncCall,请看AsyncCall的run方法
      executorService.execute(this)
      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!
      }
    }
  }

  override fun run() {
    threadName("OkHttp ${redactedUrl()}") {
      var signalledCallback = false
      //任务超时计算
      timeout.enter()
      try {
        /**拦截器 client.interceptors、RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor**/ //进行网络请求并返回结果
        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 {
      //将执行完的请求移出运行队列(runningAsyncCalls),并将等待队列(readyAsyncCalls)中的请求加入到执行队列中,使用线程池执行请求
        client.dispatcher.finished(this)
      }
    }
  }
}

在AsyncCall(Runnable)类的run()方法可以看到,它主要的流程分为3步:

  1. 执行请求并返回结果。在getResponseWithInterceptorChain()方法源码中可以看到,它依次通过自定义拦截器(client.interceptors)、重试及重定向拦截器(RetryAndFollowUpInterceptor)、桥接拦截器(BridgeInterceptor:主要功能将Request请求转化成网络请求,并执行请求,将请求返回的结果(response)解析成用户可用的response)、缓存策略拦截器(CacheInterceptor)、连接拦截器(ConnectInterceptor:与服务端建立连接,并且获得通向服务端的输入和输出流对象)、读取拦截器(CallServerInterceptor)后的结果进行解析返回 (由于篇章问题,拦截器的讲解请期待下一篇文章)

  2. 通过回调方式将执行结果返回

  3. 将执行完的请求移出运行队列(runningAsyncCalls),并将等待队列(readyAsyncCalls)中的请求加入到执行队列中,使用线程池执行请求。

Dispatcher类的finished方法

kotlin 复制代码
internal fun finished(call: AsyncCall) {
 //该请求的域名计数减1
 call.callsPerHost.decrementAndGet()
 finished(runningAsyncCalls, call)
}
// callsPerHost-1
public final int decrementAndGet() {
   return (int)VALUE.getAndAdd(this, -1) - 1;
}

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
 }
 //将等待队列的任务加入到执行队列里面并执行任务
 val isRunning = promoteAndExecute()

 if (!isRunning && idleCallback != null) {
   idleCallback.run()
 }
}

在Dispatcher类的finished方法可以看到,它主要的逻辑是将同域名请求的计数器值减1并将该任务移出执行队列,等待队列加入执行队列的逻辑请前往promoteAndExecute方法源码分析查看。

execute(同步执行)

流程图

源码

kotlin 复制代码
override fun execute(): Response {
  //检查请求是否正在执行
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  //请求执行时间超时判断
  timeout.enter()
  //执行监听回调
  callStart()
  try {
    //client.dispatcher-->Dispatcher() -->executed(call: RealCall) --> runningSyncCalls.add(call)
    client.dispatcher.executed(this)
    /**拦截器 client.interceptors、RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor**/
    //进行网络请求并返回结果
    return getResponseWithInterceptorChain()
  } finally {
    //client.dispatcher-->Dispatcher() --> finished(call: RealCall) -->finished(runningSyncCalls, call)
    client.dispatcher.finished(this)
  }
}

在execute()方法可以看到,它主要的流程分为3步:

  1. eventListener监听执行callStart方法。在RealCall类中可以看到enventListener实际是OkHttpClient中Builder对象的eventListenerFactory属性,开发者可以传入回调监听。eventListenerFactory默认是okhttp3包下的EventListener类,空实现。

  2. 执行请求并返回结果。在getResponseWithInterceptorChain()方法源码中可以看到,它依次通过自定义拦截器(client.interceptors)、重试及重定向拦截器(RetryAndFollowUpInterceptor)、桥接拦截器(BridgeInterceptor:主要功能将Request请求转化成网络请求,并执行请求,将请求返回的结果(response)解析成用户可用的response)、缓存策略拦截器(CacheInterceptor)、连接拦截器(ConnectInterceptor:与服务端建立连接,并且获得通向服务端的输入和输出流对象)、读取拦截器(CallServerInterceptor)后的结果进行解析返回 (由于篇章问题,拦截器的讲解请期待下一篇文章)

  3. 将执行完的请求移出运行队列(runningAsyncCalls),并将等待队列(readyAsyncCalls)中的请求加入到执行队列中,使用线程池执行请求。(请看Dispatcher的finish方法分析)

okhttp的同步请求、异步请求主干源码分析到此已经告一段落了,我们下一章再见~

相关推荐
这孩子叫逆2 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
星星法术嗲人6 分钟前
【Java】—— 集合框架:Collections工具类的使用
java·开发语言
一丝晨光25 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
天上掉下来个程小白27 分钟前
Stream流的中间方法
java·开发语言·windows
xujinwei_gingko38 分钟前
JAVA基础面试题汇总(持续更新)
java·开发语言
liuyang-neu39 分钟前
力扣 简单 110.平衡二叉树
java·算法·leetcode·深度优先
一丝晨光1 小时前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
罗曼蒂克在消亡1 小时前
2.3MyBatis——插件机制
java·mybatis·源码学习
GEEKVIP1 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
_GR1 小时前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划