概述

OKHttp是一个基于HTTP协议的网络恳求结构,它支撑HTTP/2协议,衔接复用和衔接池,缓存战略等功用。它的中心规划是阻拦器(Interceptor),它将恳求的杂乱逻辑切分红多个独立的模块,并经过职责链办法串联起来。每个阻拦器都有自己的职责,比方重试和重定向,桥接,缓存,衔接,调用服务器等。阻拦器之间经过RealInterceptorChain类来传递恳求和呼应,终究完结整个网络恳求的过程。

制作器

以一段简单代码示例开端

// enqueue需求callback;execute不需求,但需求自行try catch
// 这儿疏忽两个build的参数装备
// 整段代码拆解为几步
// 1. OkHttpClient.Builder().build()制作OkHttpClient
// 2. Request.Builder().build()制作Request
// 3. client.newCall(request)得到Call
// 4. call.enqueue()建议恳求
OkHttpClient.Builder().build().newCall(Request.Builder().build()).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        TODO("Not yet implemented")
    }
    override fun onResponse(call: Call, response: Response) {
        TODO("Not yet implemented")
    }
})

OkHttpClient.newCall():将Request封装成RealCall,并持有Client,为的便是关于每一个Request,能够便当的调用Client的能力,而防止client.xxx(request)

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
)

建议恳求

RealCall.execute()\enqueue():恳求的起点,均以client.dispatcher进行分发。其中exectue直接回来了Response,阐明现已完结了恳求,成果来源于getResponseWithInterceptorChain(),先记住。由于exectue直接在建议调用的线程进行恳求,而enqueue需求进行线程池调度,那么终究应该也会调用该办法建议恳求,并回来成果

override fun execute(): Response {
  // 校验,确保一个Request只恳求一次
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  // 这儿主要触及一个EventListener,支撑监听request的各个阶段和状况,比方connectEnd、dnsStart……
  timeout.enter()
  callStart()
  try {
    // 这一段便是正主,留意executed需求try包裹
    client.dispatcher.executed(this)
    return getResponseWithInterceptorChain()
  } finally {
    // 完毕
    client.dispatcher.finished(this)
  }
}
override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  callStart()
  // enqueue的话不立刻履行
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

调度器

Dispatcher:主要担任行列调度和线程切换,三个行列的释义一望而知,就不多作解释了,重视下线程池的装备吧

private var executorServiceOrNull: ExecutorService? = null
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
  get() {
    if (executorServiceOrNull == null) {
      // 解析下此处线程池的装备,假如不太清楚的,要去补补课喔
      // 中心线程池巨细为0,即直接入队blockQueue
      // 堵塞行列为SynchronousQueue,size为0,且同步堵塞,进入下一步最大线程数判别
      // 最大线程数为Int.MAX_VALUE,那么综上即每次需求就新起一个线程,达到60s不工作即收回
      // 那么这儿有个问题,这样设置线程池会不会爆,由于不设上限的话
      // 最大轻轻数maxRequests:64 和 最大恳求域名数maxRequestsPerHost:5会进行约束,后边会看到
      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<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>()

简单介绍了Dispatcher,接着看看调度的具体完成。同步调用就直接被放在了对应的运转行列里,而异步调用除了需求进入等候行列外,需求履行promoteAndExecute()

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()
}
@Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
}
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
}
fun executeOn(executorService: ExecutorService) {
  client.dispatcher.assertThreadDoesntHoldLock()
  var success = false
  try {
    // 建议线程池调度
    executorService.execute(this)
    success = true
  } catch (e: RejectedExecutionException) {
    val ioException = InterruptedIOException("executor rejected")
    ioException.initCause(e)
    noMoreExchanges(ioException)
    // callback,也便是一开端设置的回调呼应
    responseCallback.onFailure(this@RealCall, ioException)
  } finally {
    if (!success) {
      // 完毕,回顾下execute建议时是不是也有这个,对这个办法不列出来了,直接讲一下效果
      // 1.完结的request需求进行出队调度
      // 2.调用promoteAndExecute(),推动等候行列的运转,这个是等候行列得以持续运动的中心
      // 3.假如没有要运转的,也便是处于搁置状况,会运转idleCallback,类似于IdleHandler的规划
      client.dispatcher.finished(this) // This call is no longer running!
    }
  }
}

到这块,对Dispatcher的能力也大致有了必定的认知:

  1. 保护线程池
  2. 保护、办理、调度 运转\等候行列,类似于MessageQueue,而Dispatcher就适当所以Looper,那Request或许说RealCall就适当所以Message.Runnable

上面追踪到executorService.execute(this),这意味着RealCall完成了Runnable接口,找一下run办法吧。然后咱们发现了response来源于getResponseWithInterceptorChain(),再回顾下execute(),是不是同样调用了这个办法

override fun run() {
    threadName("OkHttp ${redactedUrl()}") {
      var signalledCallback = false
      timeout.enter()
      try {
        val response = getResponseWithInterceptorChain()
        signalledCallback = true
        // 异步恳求的成果不同于同步直接return,而是经过responseCallback进行回调
        // 假如需求多回调设置,那能够承继RealCall,保护callbackList,进行分发
        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)
      }
    }
  }
}

那么OkHttpClient基础阶段的规划现已浮出水面了,那接下去便是重头戏getResponseWithInterceptorChain的解析了

框架解读 | OkHttp设计剖析

中心办法getResponseWithInterceptorChain

依据getResponseWithInterceptorChain办法名,大约能够猜测这是阻拦器和职责链办法的一种结合,先过一下职责链办法吧

职责链

职责链办法的规划在于中心接口,比方有个run(),然后各链子类需求完成该接口,重写 run() 中,需求重视自身所需履行逻辑的机遇调度下流的机遇,比较类似于在树结构递归DFS关于前中后序列遍历时的感觉

1. 单链表式

这种办法,通常经过上下流的目标持有传递,来构成全体的链。在Android中事情分发就采用了这种规划,但也是由于view的树形结构的原因

// 该代码段取自菜鸟教程
public class ChainPatternDemo {
   private static AbstractLogger getChainOfLoggers(){
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
      return errorLogger;  
   }
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is a debug level information.");
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}
// 输出成果
Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.
public abstract class AbstractLogger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;
    protected int level;
    //职责链中的下一个元素
    protected AbstractLogger nextLogger;
    public void setNextLogger(AbstractLogger nextLogger){
        this.nextLogger = nextLogger;
    }
    public void logMessage(int level, String message){
        if(this.level <= level){
            write(message);
        }
        if(nextLogger !=null){
            nextLogger.logMessage(level, message);
        }
    }
    abstract protected void write(String message);
}
public class ConsoleLogger extends AbstractLogger {
   public ConsoleLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
public class ErrorLogger extends AbstractLogger {
   public ErrorLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
public class FileLogger extends AbstractLogger {
   public FileLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

2. 数组\行列

而数组\行列办法,则是在下流目标的传递\获取办法上进行了一点变化,其他规划均一致

// 取自网络博客代码
public abstract class Handler {
    // ...
    // 处理恳求的笼统办法
    public abstract void handleRequest(Request request);
    protected void next(Request request, Handler[] handlers, int index) {
        if (index < handlers.length) {  // 假如有后继节点,则转发恳求
            handlers[index].handleRequest(request);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        Handler[] handlers = new Handler[] {new ConcreteHandlerA(), new ConcreteHandlerB()};
        Request request = new Request();
        handlers[0].handleRequest(request, handlers, 1);  // 发送恳求到链头
    }
}

然后咱们来看正主的规划,看着好像像是集合进行保护,建议调用的当地是proceed,成果也由它回来。为了印证咱们的思路,跟着源码和注释看下去

fun getResponseWithInterceptorChain(): Response {
  // Build a full stack of interceptors.
  // 组链:这儿的顺序需求记住
  val interceptors = mutableListOf<Interceptor>()
  interceptors += client.interceptors
  interceptors += RetryAndFollowUpInterceptor(client)
  interceptors += BridgeInterceptor(client.cookieJar)
  interceptors += CacheInterceptor(client.cache)
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    interceptors += client.networkInterceptors
  }
  interceptors += CallServerInterceptor(forWebSocket)
  // 这儿将链全部投入,封装为RealInterceptorChain,留意这个0,在入参中代表index
  val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
      client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
  var calledNoMoreExchanges = false
  try {
    // 建议调用,获取成果,接受下面的源码
    val response = chain.proceed(originalRequest)
    if (transmitter.isCanceled) {
      response.closeQuietly()
      throw IOException("Canceled")
    }
    return response
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw transmitter.noMoreExchanges(e) as Throwable
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null)
    }
  }
}
override fun proceed(request: Request): Response {
    return proceed(request, transmitter, exchange)
}
@Throws(IOException::class)
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
    // ……
    // Call the next interceptor in the chain.
    // next这儿对Index+1
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
    // index的效果在这,表明当时节点
    val interceptor = interceptors[index]
    // 这个规划便是职责链的办法,上游能够调度下流,把next传入
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
    // ……
    return response
}

咱们将next称作下流,interceptor称作当时节点,或许看作上游,得到如下的职责链伪代码

1. 事前处理 this.doBefore()
    1.1 传递处理 next.interceptor -> return nextResponse
    1.2 得到下流回来的成果nextResponse
2. 过后处理 this.doAfter(nextResponse)
比照一下事情分发的职责链思路,当然这儿省掉了关于记忆性查找(即child找到树立后不用再找)的一些细节
1. 事前阻拦阶段 viewgroup.intercepter
    1.1 传递处理 child.dispatchTouchEvent -> child.onTouchEvent return result
    1.2 得到回传成果result
2. 过后处理 handled……

阻拦器

理解了职责链的全体运转思路后,大约明白了整个恳求的履行思路了,那咱们就需求跟着链chain的目标Interceptor逐一看看,他们都做了什么

val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)

依据如上代码,得到链顺序如图:最先的是

  1. 自定义interceptors
  2. RetryAndFollowUpInterceptor:重定向阻拦器,担任处理恳求反常、重试和重定向的逻辑
  3. BridgeInterceptor:桥接阻拦器,担任把用户结构的恳求转换为发送到服务器的恳求,把服务器回来的呼应转换为用户友好的呼应
  4. CacheInterceptor:缓存阻拦器,担任依据缓存战略和呼应头判别是否运用缓存或网络,以及更新缓存
  5. ConnectInterceptor:衔接阻拦器,担任树立衔接,挑选路由和协议
  6. 自定义networkInterceptors
  7. CallServerInterceptor:调用服务器阻拦器,担任向服务器发送恳求和接纳呼应

框架解读 | OkHttp设计剖析

自定义阻拦器

比方咱们或许需求输出接口日志、处理接口公参等情况,就能够用到这个。当然假如需求关于header、body等其他细节的话,那就应该放在networkInterceptors中,至于放在哪,完全看你处理的机遇和内容。当然,必定需求proceed(chain.request())传递下去

OkHttpClient.Builder()
    .connectTimeout(builder.timeout, TimeUnit.SECONDS)
    .readTimeout(builder.timeout, TimeUnit.SECONDS)
    .writeTimeout(builder.timeout, TimeUnit.SECONDS)
    .addInterceptor(LogInterceptor())
    .build()
class LogInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        1. 事前
        val request = chain.request()
        // 办法细节疏忽
        generateRequestLog(request).let {
            Logger.json(it)
        }
        2. 递归
        val response = chain.proceed(request)
        3. 过后
        getResponseText(response)?.let {
            Logger.json(it)
        }
        return response
    }
}

然后会将五大阻拦器,但全体不会具体打开,仅对部分源码作注,由于细节点过多,关于全体结构规划来说,能够不过多重视

RetryAndFollowUpInterceptor

处理恳求反常、重试或重定向的逻辑

override fun intercept(chain: Interceptor.Chain): Response {
  var request = chain.request()
  val realChain = chain as RealInterceptorChain
  val transmitter = realChain.transmitter()
  var followUpCount = 0
  var priorResponse: Response? = null
  while (true) {
    // 死循环,以用于重试,假如不需求的话,进行break或return
    var response: Response
    var success = false
    try {
      // 实战训练,得到response
      response = realChain.proceed(request, transmitter, null)
      success = true
    } 
    // ……
    val exchange = response.exchange
    // 这儿是重定向判别
    val route = exchange?.connection()?.route()
    // 重试判别的要害,return的情况就意味着不需求重试了
    val followUp = followUpRequest(response, route)
    if (followUp == null) {
      if (exchange != null && exchange.isDuplex) {
        transmitter.timeoutEarlyExit()
      }
      return response
    }
    val followUpBody = followUp.body
    if (followUpBody != null && followUpBody.isOneShot()) {
      return response
    }
    response.body?.closeQuietly()
    if (transmitter.hasExchange()) {
      exchange?.detachWithViolence()
    }
    // 重试次数约束
    if (++followUpCount > MAX_FOLLOW_UPS) {
      throw ProtocolException("Too many follow-up requests: $followUpCount")
    }
    request = followUp
    priorResponse = response
  }
}

BridgeInterceptor

修正恳求或呼应的头部信息,例如增加 Content-Type, Content-Length, Host, Cookie, User-Agent

override fun intercept(chain: Interceptor.Chain): Response {
  val userRequest = chain.request()
  val requestBuilder = userRequest.newBuilder()
  val contentType = body.contentType()
  if (contentType != null) {
    requestBuilder.header("Content-Type", contentType.toString())
  }
  // Content-Type
  // Content-Length\Transfer-Encoding
  // Host
  // Connection
  // Accept-Encoding
  // Cookie
  // User-Agent
  // ……
  val networkResponse = chain.proceed(requestBuilder.build())
  // 这儿还对response进行cookie解析
  cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
  val responseBuilder = networkResponse.newBuilder()
      .request(userRequest)
  // 这儿假如response是Gzip的话,还会进行解压
  if (transparentGzip &&
      "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
      networkResponse.promisesBody()) {
    val responseBody = networkResponse.body
    if (responseBody != null) {
      val gzipSource = GzipSource(responseBody.source())
      val strippedHeaders = networkResponse.headers.newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build()
      responseBuilder.headers(strippedHeaders)
      val contentType = networkResponse.header("Content-Type")
      responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
    }
  }
  return responseBuilder.build()
}

CacheInterceptor

完成缓存功用,进步恳求功率和节约流量。留意只缓存Get办法,由于Post认为是触及增删改等状况变更情况确认的,是需求c-s确认的,所以不能用缓存。具体的一些战略细节在CacheStrategy

// 创立一个缓存目录和巨细
File cacheDirectory = new File(context.getCacheDir().getAbsolutePath(), "HttpCache");
int cacheSize = 10 \* 1024 \* 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);
// 创立一个 OkHttpClient 并设置缓存
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
  • CacheInterceptor 会依据恳求和呼应的 Cache-Control 首部来判别是否运用缓存或网络,以及更新缓存

  • 假如恳求有 Cache-Control: no-cache 或 Cache-Control: max-age=0,那么会强制运用网络,不运用缓存。

  • 假如恳求有 Cache-Control: only-if-cached,那么会强制运用缓存,不运用网络。

  • 假如恳求没有特别的 Cache-Control,那么会依据呼应的 Cache-Control 来判别是否运用缓存或网络,以及缓存的有用期。

  • 假如呼应有 Cache-Control: no-store,那么会强制不运用缓存,不更新缓存。

  • 假如呼应有 Cache-Control: no-cache 或 Cache-Control: must-revalidate,那么会强制进行再验证,即向服务器发送 If-None-Match 或 If-Modified-Since 首部来判别缓存是否有用。

  • 假如呼应有 Cache-Control: max-age=n,那么会设置缓存的有用期为 n 秒,在此期间能够直接运用缓存,不需求再验证。

// 缓存保存在client中
override fun intercept(chain: Interceptor.Chain): Response {
  // 取缓存
  val cacheCandidate = cache?.get(chain.request())
  val now = System.currentTimeMillis()
  // now时刻戳也是影响缓存战略的要素之一
  val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
  // networkRequest:网络恳求,null 表明是不运用网络
  // cacheResponse:呼应缓存,null 表明不运用缀存
  val networkRequest = strategy.networkRequest
  val cacheResponse = strategy.cacheResponse
  cache?.trackResponse(strategy)
  // 缓存有,但无法呼应,代表不可用
  if (cacheCandidate != null && cacheResponse == null) {
    // The cache candidate wasn't applicable. Close it.
    cacheCandidate.body?.closeQuietly()
  }
  // If we're forbidden from using the network and the cache is insufficient, fail.
  // 不需求恳求,但缓存也无法呼应,回来504
  if (networkRequest == null && cacheResponse == null) {
    return Response.Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(HTTP_GATEWAY_TIMEOUT)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(EMPTY_RESPONSE)
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
  }
  // If we don't need the network, we're done.
  // 不需求恳求,经过上面的判别,这时cacheResponse必定有,那就回来缓存成果
  if (networkRequest == null) {
    return cacheResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build()
  }
  var networkResponse: Response? = null
  try {
    // 递归传递
    networkResponse = chain.proceed(networkRequest)
  } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
      cacheCandidate.body?.closeQuietly()
    }
  }
  // If we have a cache response too, then we're doing a conditional get.
  if (cacheResponse != null) {
    // 回来code 304,那就运用缓存
    // 和上面不一样的是,这时恳求现已完结,但与服务端洽谈后运用缓存,会更新部分信息
    if (networkResponse?.code == HTTP_NOT_MODIFIED) {
      val response = cacheResponse.newBuilder()
          .headers(combine(cacheResponse.headers, networkResponse.headers))
          .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
          .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build()
      networkResponse.body!!.close()
      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      cache!!.trackConditionalCacheHit()
      cache.update(cacheResponse, response)
      return response
    } else {
      cacheResponse.body?.closeQuietly()
    }
  }
  val response = networkResponse!!.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build()
  if (cache != null) {
    if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
      // 更新缓存
      val cacheRequest = cache.put(response)
      return cacheWritingResponse(cacheRequest, response)
    }
    if (HttpMethod.invalidatesCache(networkRequest.method)) {
      try {
        // 缓存失效,移除
        cache.remove(networkRequest)
      } catch (_: IOException) {
        // The cache cannot be written.
      }
    }
  }
  return response
}

缓存战略概括如图:

框架解读 | OkHttp设计剖析

ConnectInterceptor

树立衔接,挑选最优的路由和协议

override fun intercept(chain: Interceptor.Chain): Response {
  val realChain = chain as RealInterceptorChain
  val request = realChain.request()
  val transmitter = realChain.transmitter()
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  val doExtensiveHealthChecks = request.method != "GET"
  val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)
  return realChain.proceed(request, transmitter, exchange)
}

其主要工作内容不作源码层面的打开,概括如下(先描绘,后代码): • 依据恳求的 URL 和 OkHttpClient 的装备,挑选一个 RouteSelector 目标,用于寻找最优的路由和地址。

经过 RouteSelector 获取一个 Connection 目标,假如没有可用的衔接,就创立一个新的衔接。

private fun findConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean
): RealConnection {

经过 Connection 获取一个 RealConnection 目标,用于树立 Socket 衔接,并经过 Okio 获取输入流和输出流。

private fun findHealthyConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean,
  doExtensiveHealthChecks: Boolean
): RealConnection

依据恳求的协议,创立一个 ExchangeCodec 目标,用于编码恳求和解码呼应。

internal fun newCodec(client: OkHttpClient, chain: Interceptor.Chain): ExchangeCodec

将 StreamAllocation, RealConnection 和 ExchangeCodec 封装成一个 Exchange 目标,用于和服务器进行通信

val result = Exchange(this, call, eventListener, exchangeFinder!!, codec)

将 Exchange 传递给下一个阻拦器 CallServerInterceptor,用于向服务器发送恳求和接纳呼应。

// newExchange的回来内容便是上面的reuslt
val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)
return realChain.proceed(request, transmitter, exchange)

CallServerInterceptor

直接和服务器通信,发送恳求和接纳呼应

override fun intercept(chain: Interceptor.Chain): Response {
    // 内容许多,但主要就两个办法
    // 关于request、response的操作,均依据 协议和编码 进行输入\输出流操作
    // 发送恳求头,exchange是上游ConnectInterceptor构建传递下来的
    exchange.flushRequest()
    // 解析呼应头
    responseBuilder = exchange.readResponseHeaders(expectContinue = true)
    // 其他有一些处理,有需求可自行探索,不作打开了
}

总结

OkHttp 的源码结构规划是基于职责链办法和工厂办法的。职责链办法是指将恳求和呼应的处理分红多个阻拦器,每个阻拦器能够对恳求或呼应进行修正或转发,然后完成不同的功用,如重试、重定向、缓存、编解码等。工厂办法是指将目标的创立过程封装成一个工厂类,依据不同的参数或条件,回来不同的目标实例,然后完成多态和解耦,如 ConnectionPool、CallFactory、WebSocketFactory 等。

框架解读 | OkHttp设计剖析

灵魂发问

1. Okhttp 有哪些优点或特性?

支撑 HTTP/2,对一台机器的一切恳求同享同一个 Socket
内置衔接池,支撑衔接复用,减少推迟
支撑透明的 gzip 紧缩呼应体
呼应缓存能够完全防止网络重复恳求
恳求失利时自动重试主机的其他 IP,自动重定向

2. Okhttp 的恳求和呼应的流程是怎样的?

•  经过 OkHttpClient.Builder 构建一个 OkHttpClient 目标,设置一些装备参数,如超时时刻、阻拦器、缓存等。
•  经过 Request.Builder 构建一个 Request 目标,设置一些恳求参数,如 URL、办法、头部、体等。
•  经过 OkHttpClient.newCall 办法创立一个 Call 目标,表明一个网络恳求使命。
•  经过 Call.executeCall.enqueue 办法建议同步或异步恳求。
•  经过 RealCall 类完成 Call 接口,并将恳求使命加入到 Dispatcher 的同步或异步行列中。
•  经过 Dispatcher 类办理同步或异步行列,并依据最大并发数和主机数来调度使命履行。
•  经过 RealCall.getResponseWithInterceptorChain 办法创立一个职责链办法的阻拦器链,并顺次履行阻拦器的逻辑。
•  经过 RetryAndFollowUpInterceptor 阻拦器处理恳求反常、重试和重定向的逻辑。
•  经过 BridgeInterceptor 阻拦器把用户结构的恳求转换为发送到服务器的恳求,把服务器回来的呼应转换为用户友好的呼应。
•  经过 CacheInterceptor 阻拦器依据缓存战略和呼应头判别是否运用缓存或网络,以及更新缓存。
•  经过 ConnectInterceptor 阻拦器树立衔接,挑选路由和协议。
•  经过用户自定义的网络阻拦器处理一些网络层面的逻辑,如日志、监控、修正等。
•  经过 CallServerInterceptor 阻拦器向服务器发送恳求和接纳呼应,并依据协议进行编解码。
•  经过 Response 类封装呼应头和呼应体,并回来给上层调用者。

3. Okhttp 怎么完成缓存功用?它是怎么依据 Cache-Control 首部来判别缓存战略的?

Okhttp 完成缓存功用是经过 CacheInterceptor 阻拦器和 Cache 类来完成的。
在创立 OkHttpClient 目标时,能够指定一个 Cache 目标,用于存储缓存的呼应。
CacheInterceptor 阻拦器会依据恳求和呼应的 Cache-Control 首部来判别是否运用缓存或网络,以及更新缓存。
Cache-Control 首部是用于控制缓存行为的指令,它有以下几种常见的值:
    •  no-cache:表明不运用本地缓存,必须向服务器验证缓存是否有用。
    •  no-store:表明不运用本地缓存,也不更新本地缓存。
    •  only-if-cached:表明只运用本地缓存,不运用网络。
    •  max-age=n:表明本地缓存在 n 秒内有用,超过 n 秒后需求向服务器验证或从头获取。
    •  must-revalidate:表明本地缓存必须向服务器验证是否有用,假如无效则从头获取。
•  CacheInterceptor 阻拦器的工作流程大致如下:
依据恳求查找是否有匹配的缓存呼应,假如没有,则直接运用网络,并将呼应写入缓存(假如满足条件)。
假如有匹配的缓存呼应,判别是否过期或需求再验证,假如是,则向服务器发送带有验证首部的恳求,并依据服务器的呼应来决定是否运用缓存或更新缓存。
假如不过期或不需求再验证,则直接运用缓存,并增加 Age 首部来表明缓存的新鲜度。

4. Okhttp 怎么完成重试和重定向功用?

Okhttp 完成重试和重定向功用是经过 RetryAndFollowUpInterceptor 阻拦器来完成的。
在发送恳求前,会依据 OkHttpClient 的装备,创立一个 RouteSelector 目标,
用于寻找最优的路由和地址。假如恳求失利或收到重定向的呼应,
会依据 RouteSelector 的战略来挑选是否重试或重定向,并更新恳求的 URL 和头部信息

5. Okhttp 怎么自定义阻拦器?你有没有运用过或编写过自己的阻拦器?

经过完成 Interceptor 接口,Interceptor 接口只要一个办法 intercept,
该办法接纳一个 Chain 参数,表明阻拦器链。
在 intercept 办法中,能够对恳求或呼应进行修正或转发,
而且能够决定是否继续传递给下一个阻拦器。
在创立 OkHttpClient 目标时,能够经过 addInterceptor 或 addNetworkInterceptor 办法来增加自定义的应用阻拦器或网络阻拦器。
我有运用过或编写过自己的阻拦器,例如:
•  一个日志阻拦器,用于打印恳求和呼应的信息,方便调试和监控。
•  一个加密阻拦器,用于对恳求参数进行加密,确保数据的安全性。
•  一个认证阻拦器,用于对恳求增加认证信息,如 token、签名等,完成用户的身份验证。

6. Okhttp 怎么完成同步和异步的恳求办法?它是怎么处理回谐和反常的?

Okhttp 完成同步和异步的恳求办法是经过 Call 接口和 RealCall 类来完成的。
Call 接口表明一个网络恳求使命,它有两个办法 execute 和 enqueue。
execute 办法用于建议同步恳求,它会堵塞当时线程直到获取呼应,并回来一个 Response 目标。
enqueue 办法用于建议异步恳求,它会将恳求使命加入到 Dispatcher 的异步行列中,并传入一个 Callback 参数。
Callback 参数是一个回调接口,它有两个办法 onResponse 和 onFailure。
onResponse 办法用于处理成功的呼应,它会在异步线程中回调,并传入一个 Call 和一个 Response 参数。
onFailure 办法用于处理失利的反常,它也会在异步线程中回调,并传入一个 Call 和一个 IOException 参数。

7. Okhttp 怎么办理衔接池和线程池?它是怎么复用和收回衔接的?

Okhttp 办理衔接池和线程池是经过 ConnectionPool 类和 Dispatcher 类来完成的。
ConnectionPool 类表明一个衔接池,它保护了一个双端行列,用于存储闲暇的衔接。
它有一个整理线程,用于定时检查衔接是否过期或超过最大闲暇数,并将其移除。
Dispatcher 类表明一个调度器,它保护了三个双端行列,别离用于存储同步使命、异步使命和等候履行的异步使命。
它有一个线程池,用于履行异步使命,并依据最大并发数和主机数来调度使命履行。
•  Okhttp 复用和收回衔接是经过 StreamAllocation 类和 RealConnection 类来完成的。
StreamAllocation 类表明一个流分配器,它担任办理衔接的分配和开释。
    RouteSelector 目标,用于寻找最优的路由和地址。
    RealConnection 目标,用于表明当时分配的衔接。
    release 办法,用于开释衔接,并依据衔接是否闲暇或是否能够复用来决定是否将其加入到衔接池中。
RealConnection 类表明一个实在的衔接,它封装了一个 Socket 目标,用于树立 TCP 衔接,并经过 Okio 获取输入流和输出流。
    allocationLimit 特点,用于表明该衔接能够分配给多少个流。
    noNewStreams 特点,用于表明该衔接是否能够创立新的流。
    onStreamFinished 办法,用于在流完毕时减少 allocationLimit 的值,并依据情况开释或收回衔接。

拜读

# 由浅入深,聊聊OkHttp的那些事(易懂,不繁琐)

# 浅显易懂 OkHttp 源码解析及应用实践

假如觉得本文对你有协助,不妨 点赞+保藏+重视 支撑一波,感谢必定和喜爱

当然,更期望初读点赞再读保藏三而重视