文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

从设计模式看OkHttp源码

2024-12-03 10:05

关注

前言

说到源码,很多朋友都觉得复杂,难理解。

但是,如果是一个结构清晰且完全解耦的优质源码库呢?

OkHttp就是这样一个存在,对于这个原生网络框架,想必大家也看过很多很多相关的源码解析了。

它的源码易读,清晰。所以今天我准备从设计模式的角度再来读一遍 OkHttp的源码。

主要内容就分为两类:

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

使用

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

  1. val okHttpClient = OkHttpClient() 
  2.    val request: Request = Request.Builder() 
  3.        .url(url) 
  4.        .build() 
  5.    okHttpClient.newCall(request).enqueue(object : Callback { 
  6.        override fun onFailure(call: Call, e: IOException) { 
  7.            Log.d(TAG, "onFailure: "
  8.        } 
  9.  
  10.        override fun onResponse(call: Call, response: Response) { 
  11.            Log.d(TAG, "onResponse: " + response.body?.string()) 
  12.        } 
  13.    }) 

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

大体意思我们可以先猜猜看:

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

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

okHttpClient

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

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

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

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

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

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

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

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

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

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

Request

  1. val request: Request = Request.Builder() 
  2.     .url(url) 
  3.     .build() 
  4.  
  5. //Request.kt 
  6. open class Builder { 
  7.     internal var url: HttpUrl? = null 
  8.     internal var method: String 
  9.     internal var headers: Headers.Builder 
  10.     internal var body: RequestBody? = null 
  11.  
  12.     constructor() { 
  13.       this.method = "GET" 
  14.       this.headers = Headers.Builder() 
  15.     } 
  16.  
  17.     open fun build(): Request { 
  18.       return Request( 
  19.           checkNotNull(url) { "url == null" }, 
  20.           method, 
  21.           headers.build(), 
  22.           body, 
  23.           tags.toImmutableMap() 
  24.       ) 
  25.     } 

从Request的生成代码中可以看到,用到了其内部类Builder,然后通过Builder类组装出了一个完整的有着各种参数的Request类。

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

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

我们可以通过Builder,构建了不同的Request请求,只需要传入不同的请求地址url,请求方法method,头部信息headers,请求体body即可。(这也就是网络请求中的请求报文的格式)

这种可以通过构建形成不同的表示的 设计模式 就是 建造者模式,也是用的很多,主要为了方便我们传入不同的参数进行构建对象。

又比如上面okHttpClient的构建。

newCall(request)

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

  1. //使用 
  2. val okHttpClient = OkHttpClient() 
  3. okHttpClient.newCall(request) 
  4.  
  5. //OkHttpClient.kt 
  6. open class OkHttpClient internal constructor(builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory { 
  7.   override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false
  8.  
  9. //Call接口 
  10. interface Call : Cloneable { 
  11.   fun execute(): Response 
  12.  
  13.   fun enqueue(responseCallback: Callback) 
  14.  
  15.   fun interface Factory { 
  16.     fun newCall(request: Request): Call 
  17.   } 

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

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

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

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

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

enqueue(Callback)

接下来这个方法enqueue,肯定就是okhttp源码的重中之重了,刚才说到newCall方法其实是获取了RealCall对象,所以就走到了RealCall的enqueue方法:

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

再转向dispatcher。

  1. //Dispatcher.kt 
  2.  
  3.   val executorService: ExecutorService 
  4.     get() { 
  5.       if (executorServiceOrNull == null) { 
  6.         executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS, 
  7.             SynchronousQueue(), threadFactory("$okHttpName Dispatcher"false)) 
  8.       } 
  9.       return executorServiceOrNull!! 
  10.     } 
  11.  
  12.  
  13.   internal fun enqueue(call: AsyncCall) { 
  14.     promoteAndExecute() 
  15.   } 
  16.  
  17.  
  18.   private fun promoteAndExecute(): Boolean { 
  19.     //通过线程池切换线程 
  20.     for (i in 0 until executableCalls.size) { 
  21.       val asyncCall = executableCalls[i] 
  22.       asyncCall.executeOn(executorService) 
  23.     } 
  24.  
  25.     return isRunning 
  26.   } 
  27.  
  28.  
  29. //RealCall.kt 
  30.   fun executeOn(executorService: ExecutorService) { 
  31.  
  32.       try { 
  33.         executorService.execute(this) 
  34.         success = true 
  35.       }  
  36.     } 

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

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

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

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

其核心就在于共享对象,所有很多的池类对象,比如线程池、连接池等都是采用了享元模式 这一设计模式。当然,okhttp中不止是有线程池,还有连接池提供连接复用,管理所有的socket连接。

再回到Dispatcher,所以这个类是干嘛的呢?就是切换线程用的,因为我们调用的enqueue是异步方法,所以最后会用到线程池切换线程,执行任务。

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

execute(this)

  1. override fun run() { 
  2.       threadName("OkHttp ${redactedUrl()}") { 
  3.         try { 
  4.           //获取响应报文,并回调给Callback 
  5.           val response = getResponseWithInterceptorChain() 
  6.           responseCallback.onResponse(this@RealCall, response) 
  7.         } catch (e: IOException) { 
  8.           if (!signalledCallback) { 
  9.             responseCallback.onFailure(this@RealCall, e) 
  10.           }  
  11.         } catch (t: Throwable) { 
  12.           cancel() 
  13.           if (!signalledCallback) { 
  14.              
  15.             responseCallback.onFailure(this@RealCall, canceledException) 
  16.           } 
  17.         }  
  18.       } 

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

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

  1. override fun execute(): Response { 
  2.   //... 
  3.   return getResponseWithInterceptorChain() 

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

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

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

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

getResponseWithInterceptorChain

  1. internal fun getResponseWithInterceptorChain(): Response { 
  2.     // Build a full stack of interceptors. 
  3.     val interceptors = mutableListOf() 
  4.     interceptors += client.interceptors 
  5.     interceptors += RetryAndFollowUpInterceptor(client) 
  6.     interceptors += BridgeInterceptor(client.cookieJar) 
  7.     interceptors += CacheInterceptor(client.cache) 
  8.     interceptors += ConnectInterceptor 
  9.     if (!forWebSocket) { 
  10.       interceptors += client.networkInterceptors 
  11.     } 
  12.     interceptors += CallServerInterceptor(forWebSocket) 
  13.  
  14.     val chain = RealInterceptorChain( 
  15.         interceptors = interceptors 
  16.         //... 
  17.     ) 
  18.  
  19.     val response = chain.proceed(originalRequest) 
  20.   } 

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

  1. override fun proceed(request: Request): Response { 
  2.  
  3.   //找到下一个拦截器 
  4.   val next = copy(index = index + 1, request = request) 
  5.   val interceptor = interceptors[index
  6.  
  7.   
  8.   val response = interceptor.intercept(next
  9.   return response 

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

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

  1. override fun intercept(chain: Interceptor.Chain): Response { 
  2.    //做事情A 
  3.  
  4.    response = realChain.proceed(request) 
  5.  
  6.    //做事情B 
  7.  } 

结合两段代码,会形成一条链,这条链组织了所有连接器的工作。类似这样:

拦截器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的整个流程了,这就是下期的内容了。

先预告一波:

总结

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

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

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

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

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

本文转载自微信公众号「码上积木」,可以通过以下二维码关注。转载本文请联系码上积木学公众号。

 

来源:码上积木内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯