okhttp3源码解析(10)-复盘

前言

不容易,okhttp3系列文章写到了第十篇,不说讲的多全面多透彻,可是对原理仍是有点了解了吧。这篇就作为结束篇,来复盘下,回想巩固,查缺补漏。

复盘

恳求流程

首要仍是先来回顾下okhttp3的流程,有兴趣能够再看下这个系列第一篇文章:

《okhttp3源码解析-整体流程》。

流程大致如下:

  1. 创立okhttpClient,创立request,经过okhttpClient的newCall办法创立call(RealCall),再经过call进行同步或异步恳求

  2. 关于同步恳求,在RealCall中有两步,先加入Dispatcher的runningSyncCalls,再调用getResponseWithInterceptorChain()获得response

  3. 关于异步恳求,在RealCall中将回调封装到AsyncCall后,就交给Dispatcher处理了。Dispatcher会将AsyncCall加入到readyAsyncCalls,再推进readyAsyncCalls去运转。Dispatcher会为AsyncCall指定一个线程池,并让它在异步线程中执行其execute()办法,终究也是经过getResponseWithInterceptorChain()获得response。

  4. 在getResponseWithInterceptorChain()办法中,将一切拦截器放到了interceptors,创立了一个RealInterceptorChain,让它经过职责链方式按次序去执行拦截器,终究得到response。

  5. 在RealInterceptorChain中,调用proceed(request)会创立一个新的chain(RealInterceptorChain),然后会调用Interceptor的intercept(chain)办法,得到response并回来,而Interceptor的intercept(chain)办法内又会调用proceed(request),一个串一个把request传进去,把response拿回来。

  6. 关于拦截器,次序如下: 自定义的interceptors,retryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor,自定义的networkInterceptors,CallServerInterceptor

拦截器

说完了流程,下面也对几个中心的拦截器做个小结吧:

《okhttp3源码解析(2)-拦截器 I》

《okhttp3源码解析(3)-拦截器 II》

  1. RetryAndFollowUpInterceptor,和姓名相同,就是用来重试和继续发送的拦截器。关于一些发送异常会重试,还会对response分析,依据responseCode判别是否需求继续发送request(重定向、超时、不可达之类)。

  2. BridgeInterceptor,用来处理request和response中header的拦截器。关于request,它会加上body、header、host、connection、是否压缩、User-Agent以及cookies,关于response它会保存header到cookies、解压body。

  3. CacheInterceptor,用来存取response缓存的拦截器。它会从cache里边取出对request的缓存,并创立一个CacheStrategy来判别到底是用缓存仍是发送网络恳求,拿到网络恳求的response,会对cache更新、移除等。

  4. ConnectInterceptor,用来操控衔接和流的拦截器。它会经过streamAllocation从衔接池中拿出connection,或者直接创立新的connection(触及socket、router等),并依据connection创立HttpCodec,用于对socket(sslSocket)输入输出流的读写。

  5. CallServerInterceptor,实践网络通信(socket流读写)的拦截器。它经过httpCodec能够写入request的header和body,调用flushRequest()的时分完结一次恳求(http时),完结恳求后即可经过httpCodec读取response的header,response的body在这儿并没有读取出来,而是封装了一下socket的source,在运用的时分才会去读取。

ps. 复盘了一遍,总算了解了response的body问题。

衔接与流

衔接与流主要是ConnectInterceptor里边的功用,是经过streamAllocation去完结的。

《okhttp3源码解析(4)-StreamAllocation、HttpCodec》

《okhttp3源码解析(5)-RealConnection、Http2Connection》

《okhttp3源码解析(6)-ConnectionPool、StreamAllocation补充》

获取connection的流程如下:

  1. 先取StreamAllocation中的connection。streamAllocation在RetryAndFollowUpInterceptor中创立和release,标识一次完好恳求的流分配,它持有Connection和HttpCodec。

  2. 再从ConnectionPool中获取connection。ConnectionPool中会依据传入address以及route(此时为null)取出适宜的connection,connection还会持有streamAllocation的弱引证作为符号。

  3. 假如需求创立新的routeSelection,创立新的后遍历其routes,再从ConnectionPool中获取connection。routeSelector调用next能发生新的routeSelection,routeSelection中保存有一系列Route,connectionPool中获取connection和Route有关。

  4. 不然依据selectedRoute创立一个RealConnection。RealConnection创立和connectionPool与Route有关,selectedRoute为null时,需求从从routeSelection取第一个Route赋值


说实话okhttp中的routeSelector、routeSelection、Route、Proxy我也不是很了解,又回去看了下源码,有了一些了解,大致如下:

  1. client中有proxySelector特点,默许来自build中的ProxySelector.getDefault(),终究来自Class.forName(“sun.net.spi.DefaultProxySelector”),也就是体系默许的。

  2. 在RetryAndFollowUpInterceptor中创立Address传入了client.proxySelector,proxySelector能够经过select(url.uri())拿到多个Proxy(List)

  3. 在RouteSelector中,传入的Address中有proxy(自定义的)就用它(放在容量为1的List),不然就用默许的List(多个,上一步的)

  4. 在RouteSelector中,遍历List,关于每个Proxy,由它里边的域名,再依据DNS服务器能够得到一系列的IP(InetSocketAddress),然后依据这些IP创立一系列的Route,并构成一个routeSelection

  5. 回到StreamAllocation中,routeSelection中取出Route,依据它拿到或者创立connection

  6. RealConnection实践终究就和Route有关,在connect办法中进行socket衔接


线程池没什么好说的,衔接部分却是很有意思,是我的弱项,关于HTTP的内容仍是值得再了解下的:

  1. 不论流程如何,首要都得connectSocket,这儿会进行socket衔接,并拿到输入输出流source和sink。

  2. 关于要经过 HTTP 代理建立 HTTPS 地道的,先要发送request来创立tunnel,这个过程衔接或许被close,需求屡次connectSocket进行验证,直至成功。

  3. 上面两种状况结束后,接下来是创立协议。关于HTTP1_1和H2_PRIOR_KNOWLEDGE协议,直接运用原始socket就行,关于HTTP2,需求进行TLS衔接.

  4. TlS衔接时,会先依据sslSocketFactory创立sslSocket。sslSocketFactory假如没有设置会在okhttpClient构造中运用体系默许的。sslSocket经过startHandshake开端衔接,经过getSession堵塞拿到session,能够依据sslSocket拿到unverifiedHandshake,它保存了certificate,经过验证后,将realConnection中的socket替换为sslSocket,并从sslSocket中拿到sink和source流。

  5. 关于HTTP2协议,还要创立http2Connection进行衔接。Http2Connection里边经过Http2Writer和Http2Reader来对socket的sink和source直接操作,可是外部运用需求经过Http2Stream来完结帧的读写,Http2Stream是复用的。Http2Connection读取操作是经过ReaderRunnable在线程中while循环处理的。

  6. 完结衔接后,StreamAllocation中会创立HttpCodec。关于http2Connection衔接创立Http2Codec,其他创立Http1Codec,Http1Codec直接运用输入输出流来完结网络恳求的读写,Http2Codec复杂些,需求依据流和帧处理。

  7. 在Http2Codec中,对request和response操作都是交给Http2Stream操作。Http2Stream是在writeRequestHeaders时由Http2Connection创立的,Http2Connection内有streams(key为streamId的Map)用来保存多个Http2Stream。Http2Codec的写入时需求留意header是一个帧,body也是一个帧,header帧用来翻开一个流,body帧以streamId在Http2Stream发送出去,这儿需求同步操作。承受时,Http2Connection中的ReaderRunnable会不停读取输入流,并将数据封装成帧,然后额、依据streamId找到要接收的Http2Stream,并将帧的数据传递到它的readBuffer,外部读取时会从中读取。

  8. HttpCodec接口躲藏了Http1Codec和Http2Codec的具体操作,使得网络恳求在CallServerInterceptor中体现共同。需求留意的是Http1Codec独占socket流,而Http2Codec是复用socket流的,关于HTTP2需求了解其间衔接、数据流、音讯、帧之间的联系。

再次梳理下,感觉对HTTP2了解深刻了些,究竟没有专门去学HTTP协议的内容,仍是挺菜的,找了篇别人的文章,感觉写得挺好的,尽管也很简单,但挺有帮助:

《深化 HTTP2(帧,音讯,流)》

看完上面这篇文章,感觉很多源码里边不了解的当地都恍然大悟了,比方: 流能够穿插传递、流内的帧有必要有序、客户端建议流的StreamID为奇数、 StreamID为0的帧是操控帧(特别处理)。

这儿其实还有很多内容没有去研究,比方connectTls内的源码、OkHttpClient中的各种装备以及WebSocket相关,我觉得仍是适当去学吧,深化源码不能堕入源码。

缓存

一个和网络相关的库,而且被很多开发者运用,其缓存绝对是有一手的,我也花了两篇文章来解析:

okhttp3源码解析(7)-CacheStrategy、Cache部分解析

okhttp3源码解析(8)-DiskLruCache、Cache

下面也来收拾下读写缓存的流程:

  1. CacheInterceptor中会创立一个CacheStrategy,由它来决定是否运用缓存,仍是建议网络恳求。这儿CacheStrategy会得到networkRequest和cacheResponse,用这两个目标为空与否来判别运用缓存仍是运用网络恳求,这儿还有个原始response缓存cacheCandidate,对比它和cacheResponse来判别缓存是否失效。

  2. 原始response缓存cacheCandidate是从cache里边取出的,这个cache来自OkHttpClient。OkHttpClient中的cache默许不供给,需求自己设置,它供给了一个InternalCache接口规范了缓存的办法,一起也供给了一个Cache类完结了缓存功用,所以假如不想自定义一个缓存功用,直接运用它供给的Cache类即可。

  3. Cache类里边经过internalCache目标向外供给缓存功用,完结增删查改,实践功用在Cache类中的DiskLruCache。DiskLruCache在Cache类中,不能直接操作(除了remove),主要经过DiskLruCache.Snapshot和DiskLruCache.Editor去处理。DiskLruCache.Snapshot是DiskLruCache内数据的一个快照,包括有response的header及body的文件流,能够借此创立缓存目标response(即get)。DiskLruCache.Editor是对DiskLruCache内部数据的办理工具,主要是新建和更新,一条数据或许对应多个Editor,所以它有并发操控。缓存的remove直接经过DiskLruCache并发移除内部数据就行了。

  4. DiskLruCache经过lruEntries(LinkedHashMap)来保存缓存数据,数据的单元是DiskLruCache.Entry。DiskLruCache.Entry把缓存保存在文件里边,它保持了两种文件,cleanFiles和dirtyFiles,读写别离,处理多线程修正问题。每种文件里边又有两个(ENTRY_COUNT=2)文件,分别保存header信息和body数据,还有个lengths数组用于记载它们的长度(只记载cleanFiles)。DiskLruCache.Entry中记载了currentEditor,它会用来验证多线程时是否按正确处理。

  5. DiskLruCache中还保持了一个日志体系,这个日志更多的应该算缓存信息的记载表,它会参与lruEntries的办理。日志会保存在文件里,共有三种日志文件,初始化的时分会按一定逻辑读取日志文件,日志有其格局,包括缓存数据的信息,从文件读取的日志能够从头构建出lruEntries,这时分就能对一切文件缓存做处理了。DiskLruCache还有个cleanupRunnable,在总文件size太大或者缓存数量太多等状况执行,会移除lruEntries中最旧的数据,假如日志中无意义的数据太多(例如REMOVE操作,它和康复lruEntries无关),还会重建日志。

  6. CacheInterceptor假如进行了网络恳求,而且能够缓存时,会把response经过cache保存下来。这儿Cache的Entry经过writeTo会保存response的header信息,body这时分并没有被缓存,在运用put进行缓存时,Cache会回来一个CacheRequestImpl目标,它并不是网络的Request,而是一个缓存的恳求,它会传递到CacheInterceptor中,并参与cacheWritingResponse办法,里边会对response的Source进行封装,在真实读取response中body的时分,会仿制一份到CacheRequestImpl,当response中body读取完的时分,会触发CacheRequestImpl的close办法,其close办法会经过DiskLruCache.Editor将缓存commit,终究将body数据保存到文件里边去。

又从头看了一遍,感觉整个缓存流程总算通畅了,补漏了好多。。。

Okio

Okio部分是由于看DiskLruCache中用了很多才去看源码的,尽管我用一篇文章写了下来,可是东西太多了。

okhttp3源码解析(9)-Okio

这儿也稍微调点重点,总结下吧!

  1. Segment是Okio里边贮存数据的单元,它是一个双向链表,里边用data数组数组保存数据,数据能够同享出去。SegmentPool能够用来收集和复用无效Segment。

  2. Buffer是一个可变长的Segment序列,ByteString是不可变的字节序列,Buffer完结了BufferedSource和BufferedSink接口,里边完结了很多对流读写的操作,而且这些操作都是和segment相关的。里边最中心的功用就是对segment引证的仿制,而非对字节流的仿制,大大提高了功率。当然Buffer最大的效果仍是缓存功用,防止频繁读取IO数据。

  3. RealBufferedSource和RealBufferedSink是Okio里边Sink和Source的完结类,Okio调用buffer会封装成它们,它们持有实践的sink和source流,它们内部都有一个buffer目标,对流的读写都是先保存到buffer目标,再从buffer目标读出或写入到实践的sink和source流中,起一个缓冲的效果。

  4. Okio类供给了对外暴漏接口的功用,它能将file、socket、path、stream转换成source和sink,并能将source和sink封装成buffer,okio中的操作大都是经过buffer完结的。

写得比较简单了,可是感觉串起来了,和我上篇博客写得相同,由小及大,豁然开朗。

小结

这篇文章写得不多,可是却花费了我很长的时间去从头看源码,尽管不如刚学习时的那么细致入微,可是前面九篇博客的疑问大部分都处理了,有些当地我很想贴代码出来,可是最终想了想仍是算了,由于贴出来就和前面博客相同又臭又长了,重要的是连贯的思路与了解,深化源码,而又不陷于源码。

okhttp3的源码解析也就此告一段落了,尽管也仅仅看了个大概,可是真的收获很大!