前言
OkHttp
能够说是Android
开发中最常见的网络申请框架,OkHttp
使用方便,扩展性强,功能强大,OKHttp
源码与原理也是面试中的常客
然而OKHttp
的源码内容比拟多,想要学习它的源码往往千头万绪,一时抓不住重点.
本文从几个问题登程梳理OKHttp
相干知识点,以便疾速构建OKHttp
常识体系,如果对你有用,欢送点赞~
本文次要包含以下内容
OKHttp
申请的整体流程是怎么的?OKHttp
散发器是怎么工作的?OKHttp
拦截器是如何工作的?- 利用拦截器和网络拦截器有什么区别?
OKHttp
如何复用TCP
连贯?OKHttp
闲暇连贯如何革除?OKHttp
有哪些长处?OKHttp
框架中用到了哪些设计模式?
1. OKHttp
申请整体流程介绍
首先来看一个最简略的Http
申请是如何发送的。
val okHttpClient = OkHttpClient() val request: Request = Request.Builder() .url("https://www.google.com/") .build() okHttpClient.newCall(request).enqueue(object :Callback{ override fun onFailure(call: Call, e: IOException) { } override fun onResponse(call: Call, response: Response) { } })
这段代码看起来比较简单,OkHttp
申请过程中起码只须要接触OkHttpClient
、Request
、Call
、 Response
,然而框架外部会进行大量的逻辑解决。
所有网络申请的逻辑大部分集中在拦截器中,然而在进入拦截器之前还须要依附散发器来调配申请工作。
对于散发器与拦截器,咱们在这里先简略介绍下,后续会有更加具体的解说
- 散发器:外部保护队列与线程池,实现申请调配;
- 拦截器:五大默认拦截器实现整个申请过程。
整个网络申请过程大抵如上所示
- 通过建造者模式构建
OKHttpClient
与Request
OKHttpClient
通过newCall
发动一个新的申请- 通过散发器保护申请队列与线程池,实现申请调配
- 通过五大默认拦截器实现申请重试,缓存解决,建设连贯等一系列操作
- 失去网络申请后果
2. OKHttp
散发器是怎么工作的?
散发器的次要作用是保护申请队列与线程池,比方咱们有100个异步申请,必定不能把它们同时申请,而是应该把它们排队分个类,分为正在申请中的列表和正在期待的列表, 等申请实现后,即可从期待中的列表中取出期待的申请,从而实现所有的申请
而这里同步申请各异步申请又略有不同
同步申请
synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
因为同步申请不须要线程池,也不存在任何限度。所以散发器仅做一下记录。后续依照退出队列的程序同步申请即可
异步申请
synchronized void enqueue(AsyncCall call) { //申请数最大不超过64,同一Host申请不能超过5个 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
当正在执行的工作未超过最大限度64,同时同一Host
的申请不超过5个,则会增加到正在执行队列,同时提交给线程池。否则先退出期待队列。
每个工作实现后,都会调用散发器的finished
办法,这外面会取出期待队列中的工作继续执行
3. OKHttp
拦截器是怎么工作的?
通过下面散发器的工作散发,上面就要利用拦截器开始一系列配置了
# RealCall override fun execute(): Response { try { client.dispatcher.executed(this) return getResponseWithInterceptorChain() } finally { client.dispatcher.finished(this) } }
咱们再来看下RealCall
的execute
办法,能够看出,最初返回了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( call = this,interceptors = interceptors,index = 0 ) val response = chain.proceed(originalRequest) }
如上所示,构建了一个OkHttp
拦截器的责任链
责任链,顾名思义,就是用来解决相干事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)解决申请事务,如果某个节点解决完了就能够依据理论业务需要传递给下一个节点持续解决或者返回处理完毕。
如上所示责任链增加的程序及作用如下表所示:
拦截器 | 作用 |
---|---|
利用拦截器 | 拿到的是原始申请,能够增加一些自定义header、通用参数、参数加密、网关接入等等。 |
RetryAndFollowUpInterceptor | 处理错误重试和重定向 |
BridgeInterceptor | 应用层和网络层的桥接拦截器,次要工作是为申请增加cookie、增加固定的header,比方Host、Content-Length、Content-Type、User-Agent等等,而后保留响应后果的cookie,如果响应应用gzip压缩过,则还须要进行解压。 |
CacheInterceptor | 缓存拦截器,如果命中缓存则不会发动网络申请。 |
ConnectInterceptor | 连贯拦截器,外部会保护一个连接池,负责连贯复用、创立连贯(三次握手等等)、开释连贯以及创立连贯上的socket流。 |
networkInterceptors(网络拦截器) | 用户自定义拦截器,通常用于监控网络层的数据传输。 |
CallServerInterceptor | 申请拦截器,在前置筹备工作实现后,真正发动了网络申请。 |
咱们的网络申请就是这样通过责任链一级一级的递推上来,最终会执行到CallServerInterceptor
的intercept
办法,此办法会将网络响应的后果封装成一个Response
对象并return
。之后沿着责任链一级一级的回溯,最终就回到getResponseWithInterceptorChain
办法的返回,如下图所示:
4. 利用拦截器和网络拦截器有什么区别?
从整个责任链路来看,利用拦截器是最先执行的拦截器,也就是用户本人设置request
属性后的原始申请,而网络拦截器位于ConnectInterceptor
和CallServerInterceptor
之间,此时网络链路曾经筹备好,只期待发送申请数据。它们次要有以下区别
- 首先,利用拦截器在
RetryAndFollowUpInterceptor
和CacheInterceptor
之前,所以一旦产生谬误重试或者网络重定向,网络拦截器可能执行屡次,因为相当于进行了二次申请,然而利用拦截器永远只会触发一次。另外如果在CacheInterceptor
中命中了缓存就不须要走网络申请了,因而会存在短路网络拦截器的状况。 - 其次,除了
CallServerInterceptor
之外,每个拦截器都应该至多调用一次realChain.proceed
办法。实际上在利用拦截器这层能够屡次调用proceed
办法(本地异样重试)或者不调用proceed
办法(中断),然而网络拦截器这层连贯曾经筹备好,可且仅可调用一次proceed
办法。 - 最初,从应用场景看,利用拦截器因为只会调用一次,通常用于统计客户端的网络申请发动状况;而网络拦截器一次调用代表了肯定会发动一次网络通信,因而通常可用于统计网络链路上传输的数据。
5. OKHttp
如何复用TCP
连贯?
ConnectInterceptor
的次要工作就是负责建设TCP
连贯,建设TCP
连贯须要经验三次握手四次挥手等操作,如果每个HTTP
申请都要新建一个TCP
耗费资源比拟多
而Http1.1
曾经反对keep-alive
,即多个Http
申请复用一个TCP
连贯,OKHttp
也做了相应的优化,上面咱们来看下OKHttp
是怎么复用TCP
连贯的
ConnectInterceptor
中查找连贯的代码会最终会调用到ExchangeFinder.findConnection
办法,具体如下:
# ExchangeFinder //为承载新的数据流 寻找 连贯。寻找程序是 已调配的连贯、连接池、新建连贯 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { synchronized (connectionPool) { // 1.尝试应用 已给数据流调配的连贯.(例如重定向申请时,能够复用上次申请的连贯) releasedConnection = transmitter.connection; result = transmitter.connection; if (result == null) { // 2. 没有已调配的可用连贯,就尝试从连接池获取。(连接池稍后具体解说) if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) { result = transmitter.connection; } } } synchronized (connectionPool) { if (newRouteSelection) { //3. 当初有了IP地址,再次尝试从连接池获取。可能会因为连贯合并而匹配。(这里传入了routes,下面的传的null) routes = routeSelection.getAll(); if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) { foundPooledConnection = true; result = transmitter.connection; } } // 4.第二次没胜利,就把新建的连贯,进行TCP + TLS 握手,与服务端建设连贯. 是阻塞操作 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); synchronized (connectionPool) { // 5. 最初一次尝试从连接池获取,留神最初一个参数为true,即要求 多路复用(http2.0) //意思是,如果本次是http2.0,那么为了保障 多路复用性,(因为下面的握手操作不是线程平安)会再次确认连接池中此时是否已有同样连贯 if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) { // 如果获取到,就敞开咱们创立里的连贯,返回获取的连贯 result = transmitter.connection; } else { //最初一次尝试也没有的话,就把刚刚新建的连贯存入连接池 connectionPool.put(result); } } return result; }
下面精简了局部代码,能够看出,连贯拦截器应用了5种办法查找连贯
- 首先会尝试应用 已给申请调配的连贯。(已调配连贯的状况例如重定向时的再次申请,阐明上次曾经有了连贯)
- 若没有 已调配的可用连贯,就尝试从连接池中 匹配获取。因为此时没有路由信息,所以匹配条件:
address
统一——host
、port
、代理等统一,且匹配的连贯能够承受新的申请。 - 若从连接池没有获取到,则传入
routes
再次尝试获取,这次要是针对Http2.0
的一个操作,Http2.0
能够复用square.com
与square.ca
的连贯 - 若第二次也没有获取到,就创立
RealConnection
实例,进行TCP + TLS
握手,与服务端建设连贯。 - 此时为了确保
Http2.0
连贯的多路复用性,会第三次从连接池匹配。因为新建设的连贯的握手过程是非线程平安的,所以此时可能连接池新存入了雷同的连贯。 - 第三次若匹配到,就应用已有连贯,开释刚刚新建的连贯;若未匹配到,则把新连贯存入连接池并返回。
以上就是连贯拦截器尝试复用连贯的操作,流程图如下:
6. OKHttp
闲暇连贯如何革除?
下面说到咱们会建设一个TCP
连接池,但如果没有工作了,闲暇的连贯也应该及时革除,OKHttp
是如何做到的呢?
# RealConnectionPool private val cleanupQueue: TaskQueue = taskRunner.newQueue() private val cleanupTask = object : Task("$okHttpName ConnectionPool") { override fun runOnce(): Long = cleanup(System.nanoTime()) } long cleanup(long now) { int inUseConnectionCount = 0;//正在应用的连接数 int idleConnectionCount = 0;//闲暇连接数 RealConnection longestIdleConnection = null;//闲暇工夫最长的连贯 long longestIdleDurationNs = Long.MIN_VALUE;//最长的闲暇工夫 //遍历连贯:找到待清理的连贯, 找到下一次要清理的工夫(还未到最大闲暇工夫) synchronized (this) { for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); //若连贯正在应用,continue,正在应用连接数+1 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } //闲暇连接数+1 idleConnectionCount++; // 赋值最长的闲暇工夫和对应连贯 long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } //若最长的闲暇工夫大于5分钟 或 闲暇数 大于5,就移除并敞开这个连贯 if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { // else,就返回 还剩多久达到5分钟,而后wait这个工夫再来清理 return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { //连贯没有闲暇的,就5分钟后再尝试清理. return keepAliveDurationNs; } else { // 没有连贯,不清理 cleanupRunning = false; return -1; } } //敞开移除的连贯 closeQuietly(longestIdleConnection.socket()); //敞开移除后 立即 进行下一次的 尝试清理 return 0; }
思路还是很清晰的:
- 在将连贯退出连接池时就会启动定时工作
- 有闲暇连贯的话,如果最长的闲暇工夫大于5分钟 或 闲暇数 大于5,就移除敞开这个最长闲暇连贯;如果 闲暇数 不大于5 且 最长的闲暇工夫不大于5分钟,就返回到5分钟的剩余时间,而后期待这个工夫再来清理。
- 没有闲暇连贯就等5分钟后再尝试清理。
- 没有连贯不清理。
流程如下图所示:
7. OKHttp
有哪些长处?
- 应用简略,在设计时应用了外观模式,将整个零碎的复杂性给暗藏起来,将子系统接口通过一个客户端
OkHttpClient
对立裸露进去。 - 扩展性强,能够通过自定义利用拦截器与网络拦截器,实现用户各种自定义的需要
- 功能强大,反对
Spdy
、Http1.X
、Http2
、以及WebSocket
等多种协定 - 通过连接池复用底层
TCP
(Socket
),缩小申请延时 - 无缝的反对
GZIP
缩小数据流量 - 反对数据缓存,缩小反复的网络申请
- 反对申请失败主动重试主机的其余
ip
,主动重定向
8. OKHttp
框架中用到了哪些设计模式?
- 构建者模式:
OkHttpClient
与Request
的构建都用到了构建者模式 - 外观模式:
OkHttp
应用了外观模式,将整个零碎的复杂性给暗藏起来,将子系统接口通过一个客户端OkHttpClient
对立裸露进去。 - 责任链模式:
OKHttp
的外围就是责任链模式,通过5个默认拦截器形成的责任链实现申请的配置 - 享元模式: 享元模式的外围即池中复用,
OKHttp
复用TCP
连贯时用到了连接池,同时在异步申请中也用到了线程池
总结
本文次要梳理了OKHttp
原理相干知识点,并答复了以下问题:
OKHttp
申请的整体流程是怎么的?OKHttp
散发器是怎么工作的?OKHttp
拦截器是如何工作的?- 利用拦截器和网络拦截器有什么区别?
OKHttp
如何复用TCP
连贯?OKHttp
闲暇连贯如何革除?OKHttp
有哪些长处?OKHttp
框架中用到了哪些设计模式?
如果对您有所帮忙,欢送点赞,谢谢~
相干视频:
【2021最新版】Android studio装置教程+Android(安卓)零基础教程视频(适宜Android 0根底,Android初学入门)含音视频_哔哩哔哩_bilibili
Android OkHttp原理解读——带你深刻把握OkHttp散发器与拦截器开发_哔哩哔哩_bilibili
【 Android进阶教程】——基于Okhttp的可用网络框架原理解析_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/7020027832977850381,如有侵权,请分割删除。