OkHttp源码分析(一)——主流程分析

本文主要是为了学习OkHttp源码,先从整体流程入手,分析该开源框架的主要工作原理,后续会逐步分解,学习网络框架相关知识

目录

OkHttp的优缺点

OkHttp的主流程

OkHttp核心类分析

第一步:发起请求

第二步:获取请求结果

第三步:结束请求任务

OkHttp涉及到的设计模式


OkHttp的优缺点

优缺点是比较而言,对比目前的网络框架

框架名称 优点         缺点 适用场景
OKHttp

1)支持HTTP/2,内置连接池,支持连接复用

2)通过Gzip压缩响应体数据

3)支持同步阻塞和异步阻塞两种方式

使用时需要进一步封装

1)重量级网络交互场景

2)适合中、大型以上项目

Volley 1)基于

HttpUrlConnection,是封装后的网络框架。

2)可扩展性好,可支持HttpClient、HttpUrlConnection和Okhttp

不适合大的下载或者流式传输操作,数据方法放到byte[]数组里,消耗内存

适合轻量级网络交互,网络请求频繁,传输数据量小,不适合做文件(音视频)操作(上传/下载)
Retrofit

1)底层基于okhttp,属于封装后的网络框架

2)封装好,效率高,简洁易用

3)支持RxJava

1)扩展性差

2)项目中常常RxJava+Retrofit+Okhttp组合,来搭建网络框架,具有一定学习成本

适合大型项目,重量级网络交互场景,网络请求频繁、传输数据量大

关于OkHttp我们知道:

1)OkHttp基于Socket通信,它更倾向于底层,会对Http协议进行完全的封装

可以简单理解为:Okhttp通过Socket和服务器进行了TCP连接,并将需要的请求信息按照Http协议的格式封装,通过Socket连接发送到服务器,再读取服务器的响应

2)Android 4.4后,HttpURLConnection底层实现就是OkHttp,相当于官方认证

基于此,我们探究OkHttp源码才更有意义,对网络框架的底层原理才会更加了解

OkHttp的主流程

OkHttp源码分析(一)——主流程分析_第1张图片

通过上图,我们了解OkHttp的核心流程,具体详细过程可以看下面的文字解读

  1. 通过建造者模式,构建OkHttp的客户端请求发起者OkHttpClient
  2. 通过构建者模式,构建Request的请求体,组装参数,header、method、Url等
  3. 创建Call,真正的请求在RealCall中完成,Dispatcher调度器负责管理RealCall,通过execute()或者enquene()完成同步/异步请求
  4. execute()后者enquene()最终会调用getResponseWithInterceptorChain()方法,从拦截器链条interceptors中获取结果

OkHttp核心类分析

从上面的分析可知,RealCall是Call的具体实现类,我们来看RealCall他的exectue()方法

override fun execute(): Response {
    //判断有没有执行过,没有执行进行置位标记
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    //请求超时开始计时
    timeout.enter()
    callStart()
    try {
      // 第一步 交给dispatcher开始请求
      client.dispatcher.executed(this)
      //第二步 获取请求结果
      return getResponseWithInterceptorChain()
    } finally {
      //第三步 结束请求任务
      client.dispatcher.finished(this)
    }
}

关键代码已经做了注释,可以看到,整个请求核心步骤分三步完成

第一步:发起请求

是dispatcher的execute方法,我们进入Dispatcher类来看下,Dispatcher从字面意思来看,就是调度器,主要负责管理网络请求线程,异步请求通过线程池ExecutorService来管理

@get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

@get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      promoteAndExecute()
    }

maxRequests = 64 默认最大并发请求数量是64

maxRequestPerHost = 5 默认单个Host最大并发请求量是5

继续向下看,初始化了线程池,这个后面可以单独讲

@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!!
    }

线程池初始化完成后,继续向下看

/** 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()

通过注释可以知道

readyAsyncCalls      等待的异步任务队列

runningAsyncCalls   在运行的异步任务队列

runningSyncalls       在运行的同步任务队列

这里的ArrayDeque是双端队列,什么是双端队列?

一般队列是FIFO,即first in first out,先进先出,栈是LIFO,last in fisrt out,后进先出,而双端队列结合两种数据结构的特性,可以做到两端进,可以两端弹出,也就是可以在队列的两端进行插入和删除操作。

那么第二个问题又来了,既然是双端队列,为什么选ArrayDequeLinkedList也是大小可变的双端队列,为什么没有选择它?

这个问题就涉及到链表和数组的区别了,各有优劣,说道这里可能已经知道接下来的分析,没错,ArrayDeque底层基于数组(循环数组)实现,LinkedList底层是链表实现,数组的优势占用连续内存区域、随机读取效率高,但插入和删除效率低,而链表占用非连续内存,随机读取效率低,但插入和删除效率高,这样看来,貌似不分伯仲?

实际上,在OkHttp中,网络请求也是按照先来后到的执行,后来的需要在后面排队,每次执行过网络请求时,都要遍历readyAsyncCalls,把符合条件的网络请求加入runningAsyncCalls队列中,这时,数组中数据是连续存储在内存单元的,cpu寻找时数组就会更快,而且,连续的存储方式在垃圾回收GC时,效率会优于链表,所以,应该是综合性能,最终选择ArrayDeque

上面我们是用RealCall的同步请求execute()做例子,execute()仅仅是将runningSyncalls添加了一个队列元素进去,就可以等待响应返回了,在finally里面会通过Dispatcher的finish方法将这个执行的队列任务移出去。

实际上,网络请求我们都是做异步请求,异步请求这里惠涉及到到Dispatcher的另一个核心方法

override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

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

dispatcher的enqueue方法如下:

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()
}

promoteAndExecute在Dispatcher出现多次,我们看内部实现:

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      //遍历准备执行的任务队列
      while (i.hasNext()) {
        val asyncCall = i.next()

        //执行任务队列超过最大值64的限制,跳出循环
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        //请求每个Host的数量超过最大值,跳过当前循环,执行下一个循环
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
        //从readyAsyncCalls列表中删除
        i.remove()
        // per host加1
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        //添加到runningAsyncCalls列表
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //提交到线程池去执行
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

这里有个问题不知道有没有引起你的注意:

同步方法exectue(),直接通过getResponseWithInterceptorChain拿到返回结果,那异步enqueue()呢?怎么拿到的返回结果?

上面看到最终将AsyncCall丢给了线程池去执行,那这个AsyncCall又干了什么呢?

    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)
        }
      }
    }

AsyncCall作为RealCall的内部类,实际上是一个线程,通过线程池来完成运行,它的run方法如上,作为最终执行的核心代码,可以看到,还是调用了getResponseWithInterceptorChain()来获取服务器响应,异步通过接口返回失败或者请求结果。限于篇幅,这里不再展开,后续探讨。

第二步:获取请求结果

  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf()
    //应用拦截器,用户可以自定义
    interceptors += client.interceptors
    //重试和重定向拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    //桥接拦截器
    interceptors += BridgeInterceptor(client.cookieJar)
    //缓存拦截器
    interceptors += CacheInterceptor(client.cache)
    //连接拦截器
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      //网络拦截器
      interceptors += client.networkInterceptors
    }
    //请求拦截器
    interceptors += CallServerInterceptor(forWebSocket)

    //创建责任链
    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)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

这里我们先简单介绍下各个拦截器的功能作用,毕竟本文主要是了解OkHttp的工作流程,后续将单独写一篇文章来介绍拦截器

interceptors 由开发人员自定义,一般设置公共参数

RetryAndFollowUpInterceptor 名字上可以看出一些端倪,主要负责一些初始化,重试、重定向的工作

BridgeInterceptor 负责构建网络请求,以及响应Response转化为用户可用的Response

CacheInterceptor 主要负责缓存功能,这个很重要

ConnectInterceptor 主要负责建立连接,建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec

networkInterceptors 本质上和第一个拦截器功能差不多,主要是由于位置处于倒数第二的位置,已经可以获取到返回的数据了

CallServerInterceptor 负责网络数据的请求和响应,真正的I/O操作在这里完成,也就是Socket连接

这里暂时介绍到这里,拦截器还涉及到很多内容,里面的源码也很多,后面单独做分析

第三步:结束请求任务

来看下Dispatcher的finished方法

/** 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  finished(calls: Deque, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      //从列表内移出
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    // 异步任务整理列表,将准备等待状态任务列表,移到运行列表内,交给线程池处理
    val isRunning = promoteAndExecute()
    //没有任务可执行,闲置回调不为null,回调run方法
    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

结束任务,分三步

  • 从列表中移除任务,不管同步还是异步,只是同步不会执行promoteAndExecute
  • 整理任务列表,等待的任务加入到运行任务列表中
  • 如果可执行任务数是0,进入idle状态,如果idlecallback不为空,则执行它的run方法

OkHttp涉及到的设计模式

通过OkHttp请求流程的大致分析,我们看到源码里面涉及多处设计模式,简单列举一下

OkHttpClient和Request的创建,是建造者模式

拦截器形成一个链条,是责任链模式

说这个设计模式,主要是为了说,设计模式都是为了解决不同的问题,为了代码更健壮,易扩展,但是也是有一定副作用,一些冗余在所难免,有兴趣可以深入研究下,拦截器中还会设计到其他的设计模式

你可能感兴趣的:(阅读源码,android,okhttp,责任链模式,构建者模式)