在OkHttp访问网络的过程中,有三个类充当了重要角色,分别是StreamAllocation
、RealConnection
、HttpCodec
。通过分析这三个类在网络访问中扮演的角色,可以加深对OkHttp框架的理解。
本文需要对OkHttp有一定了解:
- OkHttp使用Deliver实现线程池调度,本文暂不讨论,只讨论关键网络访问流程。
- OkHttp采用拦截器链实现网络请求的顺序访问,拦截器链的知识参考另一篇文章。
- OkHttp不同于Volley底层使用HttpUrlConnection,而是使用Socket实现Http请求。不了解Socket如何实现Http可以自行百度。
- OkHttp没有使用java.io进行流的操作,而是使用okio。不了解Okio可以自行百度稍加了解。
OkHttp的网络访问过程是通过以下几个拦截器实现的:
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor
为简单起见,这里只将如下部分拦截器作为网络访问的关键流程:
RetryAndFollowUpInterceptor
ConnectInterceptor
CallServerInterceptor
从简化版可以看出,这是一个带有重试、重定向功能的网络访问。
下面将通过拦截器的流程图分析这三个关键类。
前提知识
在分析流程图之前,需要如下前提知识。
关键类简介
HttpCodec
HttpCodec里维护了BufferedSource和BufferedSink,可以把BufferedSource看作BufferInputStream,BufferedSink看作BufferOutputStream。通过Sink,可以写入Socket实现Http请求中需要的Header和Body;通过Source,可以获取服务器返回的Header和Body。
RealConnection
RealConnection负责Socket连接,并获取HttpCodec需要的Source和Sink。
StreamAllocation
用于获取RealConnection、调用RealConnection连接、初始化HttpCodec。同时它也是OkHttp连接复用池的重要标志位。
连接复用
OkHttp的一个重要功能,就是连接复用。
它复用的就是RealConnection,即一个Socket连接。(Socket连接只需要host、port、scheme,和path无关)这样可以极大的减少建立Socket连接的开销。
这里简要说明下复用的实现原理:
get方法:
RealConnection get(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.allocations.size() < connection.allocationLimit
&& address.equals(connection.route().address)
&& !connection.noNewStreams) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}
每次return前,该connection使用次数的计数器就会+1,下面看一下计数器的源码:
public void acquire(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
其中,allocations是List<Reference<StreamAllocation>>,即计数器是通过List中add StreamAllocation实现的。
connection使用次数计数器仅会在复用连接池get以及该connection刚创建完毕时才会add,这样才能准确的监控每个connection从复用连接池中被获取的次数。同时要记住,StreamAllocation充当了被复用次数的标志。
复用连接池会定期清理长期不用的Connection,源码中会根据List<Reference<StreamAllocation>>中元素数量排序筛选出不满足要求的Connection,并将它关闭,详情可以参考ConnectionPool的cleanup()和pruneAndGetAllocationCount()方法,这里不重点分析了。
流程分析
上面抽出了网络访问的关键步骤(拦截器),以及简介了部分关键类的主要功能。
下面就 关键拦截器 + 关键类 + 复用 机制,给出流程图。
![关键流程图.png](https://img.haomeiwen.com/i6103019/01e58427d4cbb926.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)注意:
RetryAndFollowUpInterceptor中的sameConnection的源码如下:
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}
从流程图可以看出,本次访问与相邻重定向访问假如host、port、scheme均相同,访问会复用上一次的StreamAllocation以及Connection,而不是从复用连接池获取Connection,复用连接池不会计数。
从流程图分析,当重定向序列如下时,会出现下述情况:
访问序列/类对象 | hostA/aaa | → | hostA/bbb | → | hostB/kkk | → | hostA/ccc |
---|---|---|---|---|---|---|---|
StreamAllocation | sa1 | → | sa1 | → | sa2 | → | sa3 |
RealConnection | rc1 | → | rc1 | → | rc2 | → | rc1 |
HttpCodec | hc1 | → | hc2 | → | hc3 | → | hc4 |
其中名词可以这样理解:
对于链接https://www.jianshu.com/p/1036a7b68bce
hostA/aaa的hostA为https://www.jianshu.com
aaa为p/1036a7b68bce
sa1表示网络执行过程中的StreamAllocation对象,hostA/aaa和hostA/bbb都是sa1,表示同一对象。
从上述可以看出:
RealConnection
对应一个Socket连接,它是被复用连接池保存的长链接,当存在相同域名的访问时,可以被复用。故会出现上述从域名A→域名B→域名A时,Connection被复用的情况。
StreamAllocation
可以理解为RealConnection和HttpCodec的调度器,它控制着RealConnection的来源--可能来自上一次访问的connection、或是来自连接池、或是直接初始化一个新的。同时,RealConnection的Socket连接也需要通过StreamAllocation调起,并生成HttpCodec所需要的io交互的变量。
StreamAllocation
的另一个功能,就是充当复用连接池的计数器。从序列表可以看出,hostA/aaa→hostA/bbb的重定向过程中,并没有重新new一个StreamAllocation,而是直接复用上一次的(StreamAllocation中持有connection),这样效率很明显比从复用连接池拿connection要高。而且StreamAllocation的创建也与connection使用次数计数一一对应。
HttpCodec
在HttpCodec中充当一次网络访问,从序列表也可以看出,每次网络访问,即使是每次重定向,都会创建一个新的HttpCodec,用于传本次请求的参数以及拿到本次请求的返回值。
如有问题还望评论区指出,感谢!
网友评论