阅读 327

OkHttp源码剖析(二) 设计模式下的okhttp

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

OkHttp源码剖析(一) 初识okhttp

OkHttp源码剖析(二) 设计模式下的okhttp

OkHttp源码剖析(三) 任务调度器Dispatcher

OkHttp源码剖析(四) 报文读写工具ExchangeCodec

OkHttp源码剖析(五) 代理路由

OkHttp的源码中存在着很多常见的设计模式,比如工厂模式(Call.Factory、WebSocket.Factory、CacheInterceptor中的CacheStrategy.Factory等)、外观者模式(OkHttpClient)等,但OkHttp最明显最重要的是三种模式:建造者模式、责任链模式和享元模式。

建造者模式

定义:将复杂对象的构建与表示分离。建造者模式将一个复杂对象的创建过程封装起来,允许对象通过多个步骤来创建,在对象特别复杂,内部参数很多时,建造者模式就能发挥出它的优势。

若源码中有Builder这个词,大概率使用了建造者模式。

OkHttpClient

OkHttpClient我们常用的就只有默认模式,但其内部包含很多复杂对象:超时时间(Timeout),代理(proxy),缓存(cache),分发器(dispatcher),拦截器(interceptors)等等。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocketCall.Factory {
  
  // 构造函数1
  public OkHttpClient() {
    this(new Builder()); 
  }  
  
  // 构造函数2
  private OkHttpClient(Builder builder) {
    ......
  }  
  
  public Builder newBuilder() {
    return new Builder(this);
  }
  
  // Builder类
  public static final class Builder {  
    
    public Builder() { }   
    
    Builder(OkHttpClient okHttpClient) { }  
    
    //Builder类的OkHttpClient
    public OkHttpClient build() {
      return new OkHttpClient(this);
    }  
  }  
  
}
复制代码

newBuilder函数作用:

利用建造者模式实例化OkHttpClient对象:

// 实例化一个默认的HTTP客户端
OkHttpClient client = new OkHttpClient();
// 使用自定义设置创建HTTP客户端实例
OkHttpClient client = new OkHttpClient.Builder()
                                .addInterceptor(new HttpLoggingInterceptor())  //增加拦截器
                                .cache(new Cache(cacheDir, cacheSize))  //设置用于读取和写入缓存响应的响应缓存。
                                .build();
//  实例化一个500毫秒则超时的HTTP客户端实例                         
OkHttpClient eagerClient = client.newBuilder()
                                .readTimeout(500, TimeUnit.MILLISECONDS)
                                .build();   
复制代码

Request

Request类通过建造者模式通过Request.Builder生成实例,其中url代表这个Http请求的url,method指的是GET/POST等请求,header和body自然就是http请求的首部和请求体;cacheControl是缓存控制。

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

  private var lazyCacheControl: CacheControl? = null
  val isHttps: Boolean
  fun header(name: String): String? = headers[name]
  fun headers(name: String): List<String> = headers.values(name)
  fun newBuilder(): Builder = Builder(this)
  @get:JvmName("cacheControl") val cacheControl: CacheControl
   
  open class Builder {
    ...
    open fun url(url: String): Builder {}
    open fun url(url: URL) = url(url.toString().toHttpUrl())
    open fun header(name: String, value: String) 
    open fun addHeader(name: String, value: String)
    open fun removeHeader(name: String)
    open fun headers(headers: Headers)
    open fun cacheControl(cacheControl: CacheControl): Builder {}

    open fun get() = method("GET", null)
    open fun head() = method("HEAD", null)
    open fun post(body: RequestBody) = method("POST", body)

    @JvmOverloads
    open fun delete(body: RequestBody? = EMPTY_REQUEST) = method("DELETE", body)
    open fun put(body: RequestBody) = method("PUT", body)
    open fun patch(body: RequestBody) = method("PATCH", body)
    open fun method(method: String, body: RequestBody?): Builder = apply {}
		...
    open fun build(): Request {}
  }
}

复制代码

java代码:

public final class Request {

  private Request(Builder builder) {
    ......
  }

  public Builder newBuilder() {
    return new Builder(this);
  }

  public static class Builder {   
    private HttpUrl url;
    public Builder() {
      ......
    }
    private Builder(Request request) {
      this.url = request.url;
      ......
    }
    public Builder url(HttpUrl url) {}
    public Builder header(String name, String value) {}
    ......
    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);             
    }
  }
  
}
复制代码

Response类

...类似Request

责任链模式

定义

若源代码中有Chain这个词,大概率使用了责任链模式。

使用举例

一个记录传出请求和传入响应简单的拦截器:

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request();

    //前置工作
    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));

    //中置工作
    Response response = chain.proceed(request);

    
    //后置工作
    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}
复制代码

源码解析

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

    val chain = RealInterceptorChain(
        ...
    )

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest)
      ...
      return response
    } catch (e: IOException) {
      ...
    }
  }
复制代码

各层Interceptor

通过上面的责任链时序图可以看到,所有的拦截器直接最主要的操作为intercceptor(),该步骤使得责任链可以不断往下传递执行,我们将在各拦截器在intercceptor()方法之前的操作称为前置操作,在intercceptor()方法之后的操作称为前后置操作,以此来进行具体分析。

开发者自定的Interceptor

开发者使用 addInterceptor(Interceptor) 所设置的拦截器会在所有其他 Interceptor 处理之前运行,它也会收到 Response 之后,做最后的善后工作。如果有统一的 header 要添加,可以在这里设置;

RetryAndFollowUpInterceptor

负责出错重试、重定向。这层拦截器使得出错重试和重定向对开发者无感。

while (true) {
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)
      ...
        try {
          response = realChain.proceed(request)
          newExchangeFinder = true
        } catch (e: RouteException) {
         ...
        }
        ...
    }
复制代码

前置工作:

通过call.enterNetworkInterceptorExchange()方法,创建 ExchangeFinder,供在后续拦截器使用。

中置工作:

response = realChain.proceed()

后置工作:

错误重试、重定向 -> 返回response

BridgeInterceptor

桥接(从应用程序代码到网络代码的桥梁)

前置工作:

填充Http请求协议中的head头信息

中置工作:

response = realChain.proceed()

后置工作:

通过GzipSource 解开 body

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

前置工作:

判断是否有缓存,有缓存就用并提前返回;除此之外如果被禁止使用网络或者缓存不足等条件,也会提前返回。

// If we're forbidden from using the network and the cache is insufficient, fail.
    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().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }
复制代码

中置工作:

response = realChain.proceed()

后置工作:

判断响应结果是否可以缓存,可以缓存就缓存/更新缓存下来

 if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }

      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
复制代码

该Interceptor后置中,还主要实现http协议中的If-Modified-Since(304)类型请求,该字段的功能:

判断客户端缓存的数据是否已经过期,如果过期则获取新的数据

它通常应用于服务器需要返回大文件的情况,可以加快http传输速度。

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      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.also {
          listener.cacheHit(call, it)
        }
      } else {
        cacheResponse.body?.closeQuietly()
      }
    }
复制代码

ConnectInterceptor

这个拦截器是okhttp核心中的核心。

因为该拦截器只要负责连接,因而前置操作为找到一个连接,中置操作为connectedChain.proceed(),后置直接返回结果response,没有额外的后置操作。

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}
复制代码

关键是initExchange()这一行,想尽办法拿一个连接且连接健康,根据连接建立codec对象,然后把codec对象塞进Exchange里面。

 internal fun initExchange(chain: RealInterceptorChain): Exchange {
    ...
    val exchangeFinder = this.exchangeFinder!!
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    ...
    return result
  }
复制代码

ExchangeFinder类中的find方法可以看到,在拿到一个可用且健康的连接后,通过resultConnection.newCodec(client, chain)方法,从一个可用且健康的连接创立了Codec对象。

class ExchangeFinder(
 ...
) {
 ...
  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec {
    try {
      val resultConnection = findHealthyConnection(
          ...
      )
      return resultConnection.newCodec(client, chain)
    } catch (e: RouteException) {
    	...
    }
  }
  
}
复制代码

在findHealthyConnection()方法中会调用ExchangeFinder.findConnection()方法,该方法在享元模式这章中再深入探索下。

NetworkInterceptor

网络拦截器,一般使用不到,除非我们想拿到最原始的网络请求和返回,因为该拦截器的下一层就是CallServerInterceptor了,下一层直接进行网络的收发,不再有任何前置和后置操作了。

具体应用有FaceBook的stetho

CallServerInterceptor

链中的最后一个拦截器,由于它只负责和网络的收发,所以它没有前置操作和后置操作,只执行intercept中置方法。

它通过操作exchage,发请求,读响应(和io操作),对服务器进行网络调用发送内容,将字段输出到IO流当中。具体体现:

Http1.1:输入到字符串流

Http2:输入到二进制帧中,HEADER帧和DATA帧

 exchange.writeRequestHeaders(request)

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        exchange.flushRequest()
        responseBuilder = exchange.readResponseHeaders(expectContinue = true)
        exchange.responseHeadersStart()
        invokeStartEvent = false
      }
      if (responseBuilder == null) {
        ...
    } else {
      exchange.noRequestBody()
    }
复制代码

小结

平时使用框架时,只有应用拦截器和网络拦截器可以自定义。

各种拦截器都必须遵循Interceptor接口,拦截器责任链的实现类部分源码,随着index的递增,返回链条中的下一个拦截器。

RealInterceptorChain:

@Throws(IOException::class)
  override fun proceed(request: Request): Response {
    ...
    // Call the next interceptor in the chain.
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
   ...
    return response
  }
}
复制代码

Android中常见的其他责任链模式:ViewGroup将事件分发到子View。

享元模式

享元设计模式核心:池中复用

已有的对象们搁于享元池中,用到时存在即复用,否则再新建。

一般带池的概念大概率使用了享元模式:线程池,连接池,数据池,缓存池,消息池等。

ExchangeFinder.findConnection()

OkHttp3将客户端与服务器之间的连接定义为接口Connection,通过RealConnection实现,在ConnectionPool中,将连接Connection储存在一个双端队列中。

  private val connections = ConcurrentLinkedQueue<RealConnection>()
复制代码

在ConnectInterceptor中,需要调用realChain.call.initExchange(chain)找到exchange,而在initExchange中,调用了ExchangeFinder中的findConnection()方法去寻找一个可用的connection连接。连接存储在RealConnectionPool中的connections队列当中,OkHttp在寻找可用的Connection的过程当中,体现了享元模式的核心思想。

Route

route代表具体的路由,里面主要包含了连接的ip地址,端口,代理模式,还有直接取的HttpClient里的一些连接信息等。

class Route(
  @get:JvmName("address") val address: Address,
  @get:JvmName("proxy") val proxy: Proxy,
  @get:JvmName("socketAddress") val socketAddress: InetSocketAddress
) {
  ...
}

复制代码

在OkHtttp中,Route组成RouteSelection,RouteSelection实际上是Selection类,它是同样端口,同样代理类型下的不同ip的route集合。RouteSelection组成RouteSelector。这里不展开细说,只需要先关注到,在寻找连接时,需要拿到Route,然后根据Route中的ip、端口、代理等,判断连接是否可以被合并(Http2)。

连接寻找过程

ExchangeFinder.findConnection()返回一个连接来承载一个新的stream,它会优先选择已存在的stream,如果不存在, 池会最终创建一个新的连接。该方法这会在每次阻塞操作之前检查取消。

ExchangeFinder.findConnection()一共进行了五次寻找获取连接:

  • 第一次:有连接,且可用:

    call.connection 不为空且符合复用条件,直接使用并返回

  • 第二次:没连接,去池里找,非多路复用连接:

    调用 callAcquirePooledConnection 找非多路复用的链接

  • 第三次:没连接,去池里找,多路复用/非多路复用连接:

    调用 callAcquirePooledConnection 非多路/多路都拿

  • 第四次:都拿不到,我自己去创建连接

  • 第五次:在第四次基础上,尝试取只多路复用的链接,能拿到就用这个

    再次调用 callAcquirePooledConnection 拿多路复用的链接,如果拿到将第四次创建的丢弃,将result 返回

@Throws(IOException::class)
  private fun findConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean
  ): RealConnection {
    // 检查该call是否被取消,若取消,即不再进行后续操作
    if (call.isCanceled()) throw IOException("Canceled")

    // 尝试复用call中的已有的连接
    val callConnection = call.connection 
    if (callConnection != null) {
      var toClose: Socket? = null
      synchronized(callConnection) {
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          //该步骤首先检查call中已有的连接是否符合复用条件,不符合就要释放连接:releaseConnection
          toClose = call.releaseConnectionNoEvents()
        }
      }

      // call的连接不null没被释放说明通过了上面的可复用条件检查
      // 既然连接存在,还是好的链接,直接返回连接
      if (call.connection != null) {
        check(toClose == null)
        return callConnection
      }

      // call的连接释放掉了,关闭连接
      toClose?.closeQuietly()
      eventListener.connectionReleased(call, callConnection)
    }

    // 上述步骤没拿到连接,说明我们需要一个新连接,这边状态重赋值
    refusedStreamCount = 0
    connectionShutdownCount = 0
    otherFailureCount = 0

    // 第一次尝试从池中获取一个连接,
    // 第一次取时,不寻找可多路复用的连接
    // 取到连接即成功,直接返回即可 
    // 注意该步没有传route,requireMultiplexed 为 false,
    // 表示只拿非多路复用的连接(Http1.1的连接可拿,Http2原本能用的连接,拿不到)
    if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }

    // 池中什么也没有,计算我们下一个需要的route
    val routes: List<Route>?
    val route: Route
    if (nextRouteToTry != null) {
      // Use a route from a preceding coalesced connection.
      routes = null
      route = nextRouteToTry!!
      nextRouteToTry = null
    } else if (routeSelection != null && routeSelection!!.hasNext()) {
      // Use a route from an existing route selection.
      routes = null
      route = routeSelection!!.next()
    } else {
      // Compute a new route selection. This is a blocking operation!
      var localRouteSelector = routeSelector
      if (localRouteSelector == null) {
        localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
        this.routeSelector = localRouteSelector
      }
      val localRouteSelection = localRouteSelector.next()
      routeSelection = localRouteSelection
      routes = localRouteSelection.routes

      if (call.isCanceled()) throw IOException("Canceled")

      // 在有了路由route后,可以根据route找能进行连接合并(Http2)的连接
      // 第二次尝试调用 connectionPool.callAcquirePooledConnection
      // 传了route,且requireMultiplexed 为 false,
      // 表示拿多路复用或非多路复用我都拿
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
        val result = call.connection!!
        eventListener.connectionAcquired(call, result)
        return result
      }

      route = localRouteSelection.next()
    }

    // 第一次第二次都拿不到 我需要自己创立连接
    val newConnection = RealConnection(connectionPool, route)
    call.connectionToCancel = newConnection
    try {
      //创建完连接后要进行网络连接,所以是阻塞式的
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
    } finally {
      call.connectionToCancel = null
    }
    call.client.routeDatabase.connected(newConnection.route())

    // 防止创建了两个可被合并的连接,尝试取第一个先创建好的,后创建的回收掉。
    // 传了route,且requireMultiplexed 为 true,
    // 表示只能是Http2的,只拿能多路复用的连接
    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
      val result = call.connection!!
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      eventListener.connectionAcquired(call, result)
      return result
    }

    synchronized(newConnection) {
      // 自己创建完的连接,要塞进连接池,供共享使用
      connectionPool.put(newConnection)
      call.acquireConnectionNoEvents(newConnection)
    }

    eventListener.connectionAcquired(call, newConnection)
    return newConnection
  }
复制代码

注意

1.Java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。

2.Kotlin默认内部类是静态内部类,和Java相反。

参考

square.github.io/okhttp/

zhuanlan.zhihu.com/p/58093669

www.jianshu.com/p/8d69fd920…

juejin.cn/post/684490…

文章分类
Android
文章标签