从设计模式角度看OkHttp源码

前言

说到源码,不少朋友都以为复杂,难理解。html

可是,若是是一个结构清晰且彻底解耦的优质源码库呢?web

OkHttp就是这样一个存在,对于这个原生网络框架,想必你们也看过不少不少相关的源码解析了。设计模式

它的源码好看,易读,清晰,因此今天我准备从设计模式的角度再来读一遍 OkHttp的源码。缓存

主要内容就分为两类:服务器

  • okhttp的基本运做流程
  • 涉及到的设计模式

(本文源码版本为okhttp:4.9.0,拦截器会放到下期再讲)websocket

使用

读源码,首先就要从它的使用方法开始:cookie

val okHttpClient = OkHttpClient()
    val request: Request = Request.Builder()
        .url(url)
        .build()
    okHttpClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.d(TAG, "onFailure: ")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.d(TAG, "onResponse: " + response.body?.string())
        }
    })

从这个使用方法来看,我抽出了四个重要信息:网络

  • okHttpClient
  • Request
  • newCall(request)
  • enqueue(Callback)

大致意思咱们能够先猜猜看:架构

配置一个客户端实例okHttpClient和一个Request请求,而后这个请求经过okHttpClientnewCall方法封装,最后用enqueue方法发送出去,并收到Callback响应。框架

接下来就一个个去认证,并找找其中的设计模式。

okHttpClient

首先看看这个okhttp的客户端对象,也就是okHttpClient

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor()) 
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();

在这里,咱们实例化了一个HTTP的客户端client,而后配置了它的一些参数,好比拦截器、超时时间

这种咱们经过一个统一的对象,调用一个接口或方法,就能完成咱们的需求,而起内部的各类复杂对象的调用和跳转都不须要咱们关心的设计模式就是外观模式(门面模式)

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端能够访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

其重点就在于系统内部和各个子系统之间的复杂关系咱们不须要了解,只须要去差遣这个门面 就能够了,在这里也就是OkHttpClient

它的存在就像一个接待员,咱们告诉它咱们的需求,要作的事情。而后接待员去内部处理,各类调度,最终完成。

外观模式主要解决的就是下降访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。

这个模式也是三方库很经常使用的设计模式,给你一个对象,你只须要对这个对象使唤,就能够完成需求。

固然,这里还有一个比较明显的设计模式是建造者模式,下面会说到。

Request

val request: Request = Request.Builder()
    .url(url)
    .build()

//Request.kt
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()
    }

    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }
}

Request的生成代码中能够看到,用到了其内部类Builder,而后经过Builder类组装出了一个完整的有着各类参数的Request类

这也就是典型的 建造者(Builder)模式

建造者(Builder)模式,将一个复杂的对象的构建与它的表示分离,是的一样的构建过程能够建立不一样的表示。

咱们能够经过Builder,构建了不一样的Request请求,只须要传入不一样的请求地址url,请求方法method,头部信息headers,请求体body便可。
(这也就是网络请求中的请求报文的格式)

这种能够经过构建造成不一样的表示的 设计模式 就是 建造者模式,也是用的不少,主要为了方便咱们传入不一样的参数进行构建对象。

又好比上面okHttpClient的构建。

newCall(request)

接下来是调用OkHttpClient类的newCall方法获取一个能够去调用enqueue方法的接口。

//使用
val okHttpClient = OkHttpClient()
okHttpClient.newCall(request)

//OkHttpClient.kt
open class OkHttpClient internal constructor(builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory {
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
}

//Call接口
interface Call : Cloneable {
  fun execute(): Response

  fun enqueue(responseCallback: Callback)

  fun interface Factory {
    fun newCall(request: Request): Call
  }
}

newCall方法,实际上是Call.Factory接口里面的方法。

也就是建立Call的过程,是经过Call.Factory接口的newCall方法建立的,而真正实现这个方法交给了这个接口的子类OkHttpClient

那这种定义了统一建立对象的接口,而后由子类来决定实例化这个对象的设计模式就是 工厂模式

在工厂模式中,咱们在建立对象时不会对客户端暴露建立逻辑,而且是经过使用一个共同的接口来指向新建立的对象。

固然,okhttp这里的工厂有点小,只有一条生产线,就是Call接口,并且只有一个产品,RealCall

enqueue(Callback)

接下来这个方法enqueue,确定就是okhttp源码的重中之重了,刚才说到newCall方法实际上是获取了RealCall对象,因此就走到了RealCall的enqueue方法:

override fun enqueue(responseCallback: Callback) {
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

再转向dispatcher。

//Dispatcher.kt

  val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }


  internal fun enqueue(call: AsyncCall) {
    promoteAndExecute()
  }


  private fun promoteAndExecute(): Boolean {
    //经过线程池切换线程
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }


//RealCall.kt
  fun executeOn(executorService: ExecutorService) {

      try {
        executorService.execute(this)
        success = true
      } 
    }

这里用到了一个新的类Dispatcher,调用到的方法是asyncCall.executeOn(executorService)

这个executorService参数你们应该都熟悉吧,线程池。最后是调用executorService.execute方法执行线程池任务。

而线程池的概念其实也是用到了一种设计模式,叫作享元模式

享元模式(Flyweight Pattern)主要用于减小建立对象的数量,以减小内存占用和提升性能。这种类型的设计模式属于结构型模式,它提供了减小对象数量从而改善应用所需的对象结构的方式。

其核心就在于共享对象,全部不少的池类对象,好比线程池、链接池等都是采用了享元模式 这一设计模式。固然,okhttp中不止是有线程池,还有链接池提供链接复用,管理全部的socket链接。

再回到Dispatcher,因此这个类是干吗的呢?就是切换线程用的,由于咱们调用的enqueue是异步方法,因此最后会用到线程池切换线程,执行任务。

继续看看execute(this)中的this任务。

execute(this)

override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        try {
          //获取响应报文,并回调给Callback
          val response = getResponseWithInterceptorChain()
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (!signalledCallback) {
            responseCallback.onFailure(this@RealCall, e)
          } 
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            
            responseCallback.onFailure(this@RealCall, canceledException)
          }
        } 
      }

没错,这里就是请求接口的地方了,经过getResponseWithInterceptorChain方法获取响应报文response,而后经过Callback的onResponse方法回调,或者是有异常就经过onFailure方法回调。

那同步方法是否是就没用到线程池呢?去找找execute方法:

override fun execute(): Response {
    //...
    return getResponseWithInterceptorChain()
  }

果真,经过execute方法就直接返回了getResponseWithInterceptorChain,也就是响应报文。

到这里,okhttp的大致流程就结束了,这部分的流程大概就是:

设置请求报文 -> 配置客户端参数 -> 根据同步或异步判断是否用子线程 -> 发起请求并获取响应报文 -> 经过Callback接口回调结果

剩下的内容就所有在getResponseWithInterceptorChain方法中,这也就是okhttp的核心。

getResponseWithInterceptorChain

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(
        interceptors = interceptors
        //...
    )

    val response = chain.proceed(originalRequest)
  }

代码不是很复杂,就是 加加加 拦截器,而后组装成一个chain类,调用proceed方法,获得响应报文response。

override fun proceed(request: Request): Response {

    //找到下一个拦截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

   
    val response = interceptor.intercept(next)
    return response
  }

简化了下代码,主要逻辑就是获取下一个拦截器(index+1),而后调用拦截器的intercept方法。

而后在拦截器里面的代码统一都是这种格式:

override fun intercept(chain: Interceptor.Chain): Response {
    //作事情A

    response = realChain.proceed(request)

    //作事情B
  }

结合两段代码,会造成一条链,这条链组织了全部链接器的工做。相似这样:

拦截器1作事情A -> 拦截器2作事情A -> 拦截器3作事情A -> 拦截器3作事情B -> 拦截器2作事情B -> 拦截器1作事情B

应该是好理解的吧,经过proceed方法把每一个拦截器链接起来了。

而最后一个拦截器ConnectInterceptor就是分割事情A和事情B,其做用就是进行真正的与服务器的通讯,向服务器发送数据,解析读取的响应数据。

因此事情A和事情B是什么意思呢?其实就表明了通讯以前的事情和通讯以后的事情。

再来个动画:

这种思想是否是有点像..递归?没错,就是递归,先递进执行事情A,再回归作事情B。

而这种递归循环,其实也就是用到了设计模式中的 责任链模式

责任链模式(Chain of Responsibility Pattern)为请求建立了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。

简单的说,就是让每一个对象都能有机会处理这个请求,而后各自完成本身的事情,一直到事件被处理。Android中的事件分发机制也是用到了这种设计模式。

接下来就是了解每一个拦截器到底作了什么事,就能够了解到okhttp的整个流程了,这就是下期的内容了。

先预告一波:

  • addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在全部的拦截器处理以前进行最先的拦截处理,好比一些公共参数,Header均可以在这里添加。
  • RetryAndFollowUpInterceptor,这里会对链接作一些初始化工做,以及请求失败的重试工做,重定向的后续请求工做。
  • BridgeInterceptor,这里会为用户构建一个可以进行网络访问的请求,同时后续工做将网络请求回来的响应Response转化为用户可用的Response,好比添加文件类型,content-length计算添加,gzip解包。
  • CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,并且若是本地有了可⽤的Cache,就能够在没有网络交互的状况下就返回缓存结果。
  • ConnectInterceptor,这里主要就是负责创建链接了,会创建TCP链接或者TLS链接,以及负责编码解码的HttpCodec。
  • networkInterceptors,这里也是开发者本身设置的,因此本质上和第一个拦截器差很少,可是因为位置不一样,用处也不一样。这个位置添加的拦截器能够看到请求和响应的数据了,因此能够作一些网络调试。
  • CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操做,经过socket读写数据。

总结

读完okhttp的源码,感受就一个字:舒服

一份好的代码应该就是这样,各模块之间经过各类设计模式进行解耦,阅读者能够每一个模块分别去去阅读了解,而不是各个模块缠绵在一块儿,杂乱无章。

最后再总结下okhttp中涉及到的设计模式:

  • 外观模式。经过okHttpClient这个外观去实现内部各类功能。
  • 建造者模式。构建不一样的Request对象。
  • 工厂模式。经过OkHttpClient生产出产品RealCall。
  • 享元模式。经过线程池、链接池共享对象。
  • 责任链模式。将不一样功能的拦截器造成一个链。

其实仍是有一些设计模式没说到的,好比

  • websocket相关用到的观察者模式
  • Cache集合相关的迭代器模式

之后遇到了再作补充吧。

参考

https://www.runoob.com/design-pattern/design-pattern-tutorial.html
https://www.jianshu.com/p/ae2fe5481994
https://juejin.cn/post/6895369745445748749

拜拜

感谢你们的阅读,有一块儿学习的小伙伴能够关注下个人公众号——码上积木❤️❤️
每日一个知识点,聚沙成塔,创建知识体系架构。
这里有一群很好的Android小伙伴,欢迎你们加入~

相关文章
相关标签/搜索