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的同步请求、异步请求主干源码分析到此已经告一段落了,我们下一章再见~

相关推荐
Winston Wood11 分钟前
Android Parcelable和Serializable的区别与联系
android·序列化
雷神乐乐14 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
清风徐来辽16 分钟前
Android 项目模型配置管理
android
小刘|18 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
逊嘘37 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
帅得不敢出门42 分钟前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
morris13144 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员1 小时前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU1 小时前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea