来吧,今天说说经常使用的网络框架OKHttp,也是如今Android所用的原生网络框架(Android 4.4
开始,HttpURLConnection
的底层实现被Google
改为了OkHttp
),GOGOGO!java
OKHTTP
的拦截器是把全部的拦截器放到一个list里,而后每次依次执行拦截器,而且在每一个拦截器分红三部分:web
proceed
方法把请求交给下一个拦截器这样依次下去就造成了一个链式调用,看看源码,具体有哪些拦截器:设计模式
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); }
根据源码可知,一共七个拦截器:缓存
addInterceptor(Interceptor)
,这是由开发者设置的,会按照开发者的要求,在全部的拦截器处理以前进行最先的拦截处理,好比一些公共参数,Header均可以在这里添加。RetryAndFollowUpInterceptor
,这里会对链接作一些初始化工做,以及请求失败的充实工做,重定向的后续请求工做。跟他的名字同样,就是作重试工做还有一些链接跟踪工做。BridgeInterceptor
,这里会为用户构建一个可以进行网络访问的请求,同时后续工做将网络请求回来的响应Response转化为用户可用的Response,好比添加文件类型,content-length计算添加,gzip解包。CacheInterceptor
,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,并且若是本地有了可⽤的Cache,就能够在没有网络交互的状况下就返回缓存结果。ConnectInterceptor
,这里主要就是负责创建链接了,会创建TCP链接或者TLS链接,以及负责编码解码的HttpCodecnetworkInterceptors
,这里也是开发者本身设置的,因此本质上和第一个拦截器差很少,可是因为位置不一样,因此用处也不一样。这个位置添加的拦截器能够看到请求和响应的数据了,因此能够作一些网络调试。CallServerInterceptor
,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操做,经过socket读写数据。频繁的进行创建Sokcet
链接和断开Socket
是很是消耗网络资源和浪费时间的,因此HTTP中的keepalive
链接对于下降延迟和提高速度有很是重要的做用。keepalive机制
是什么呢?也就是能够在一次TCP链接中能够持续发送多份数据而不会断开链接。因此链接的屡次使用,也就是复用就变得格外重要了,而复用链接就须要对链接进行管理,因而就有了链接池的概念。websocket
OkHttp中使用ConectionPool
实现链接池,默认支持5个并发KeepAlive
,默认链路生命为5分钟。cookie
1)首先,ConectionPool
中维护了一个双端队列Deque
,也就是两端均可以进出的队列,用来存储链接。
2)而后在ConnectInterceptor
,也就是负责创建链接的拦截器中,首先会找可用链接,也就是从链接池中去获取链接,具体的就是会调用到ConectionPool
的get方法。网络
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.isEligible(address, route)) { streamAllocation.acquire(connection, true); return connection; } } return null; }
也就是遍历了双端队列,若是链接有效,就会调用acquire方法计数并返回这个链接。并发
3)若是没找到可用链接,就会建立新链接,并会把这个创建的链接加入到双端队列中,同时开始运行线程池中的线程,其实就是调用了ConectionPool
的put方法。框架
public final class ConnectionPool { void put(RealConnection connection) { if (!cleanupRunning) { //没有链接的时候调用 cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); } }
3)其实这个线程池中只有一个线程,是用来清理链接的,也就是上述的cleanupRunnable
socket
private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { //执行清理,并返回下次须要清理的时间。 long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { //在timeout时间内释放锁 try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } } };
这个runnable
会不停的调用cleanup方法清理线程池,并返回下一次清理的时间间隔,而后进入wait等待。
怎么清理的呢?看看源码:
long cleanup(long now) { synchronized (this) { //遍历链接 for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); //检查链接是不是空闲状态, //不是,则inUseConnectionCount + 1 //是 ,则idleConnectionCount + 1 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; // If the connection is ready to be evicted, we're done. long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } //若是超过keepAliveDurationNs或maxIdleConnections, //从双端队列connections中移除 if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { //若是空闲链接次数>0,返回将要到期的时间 // A connection will be ready to evict soon. return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { // 链接依然在使用中,返回保持链接的周期5分钟 return keepAliveDurationNs; } else { // No connections, idle or in use. cleanupRunning = false; return -1; } } closeQuietly(longestIdleConnection.socket()); // Cleanup again immediately. return 0; }
也就是当若是空闲链接maxIdleConnections
超过5个或者keepalive时间大于5分钟,则将该链接清理掉。
4)这里有个问题,怎样属于空闲链接?
其实就是有关刚才说到的一个方法acquire
计数方法:
public void acquire(RealConnection connection, boolean reportedAcquired) { assert (Thread.holdsLock(connectionPool)); if (this.connection != null) throw new IllegalStateException(); this.connection = connection; this.reportedAcquired = reportedAcquired; connection.allocations.add(new StreamAllocationReference(this, callStackTrace)); }
在RealConnection
中,有一个StreamAllocation
虚引用列表allocations
。每建立一个链接,就会把链接对应的StreamAllocationReference
添加进该列表中,若是链接关闭之后就将该对象移除。
5)链接池的工做就这么多,并不负责,主要就是管理双端队列Deque<RealConnection>
,能够用的链接就直接用,而后按期清理链接,同时经过对StreamAllocation
的引用计数实现自动回收。
这个不要太明显,能够说是okhttp的精髓所在了,主要体现就是拦截器的使用,具体代码能够看看上述的拦截器介绍。
在Okhttp中,建造者模式也是用的挺多的,主要用处是将对象的建立与表示相分离,用Builder组装各项配置。
好比Request:
public class Request { public static class Builder { @Nullable HttpUrl url; String method; Headers.Builder headers; @Nullable RequestBody body; public Request build() { return new Request(this); } } }
工厂模式和建造者模式相似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。
例子有CacheInterceptor拦截器中又个CacheStrategy对象:
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); public Factory(long nowMillis, Request request, Response cacheResponse) { this.nowMillis = nowMillis; this.request = request; this.cacheResponse = cacheResponse; if (cacheResponse != null) { this.sentRequestMillis = cacheResponse.sentRequestAtMillis(); this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis(); Headers headers = cacheResponse.headers(); for (int i = 0, size = headers.size(); i < size; i++) { String fieldName = headers.name(i); String value = headers.value(i); if ("Date".equalsIgnoreCase(fieldName)) { servedDate = HttpDate.parse(value); servedDateString = value; } else if ("Expires".equalsIgnoreCase(fieldName)) { expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { lastModified = HttpDate.parse(value); lastModifiedString = value; } else if ("ETag".equalsIgnoreCase(fieldName)) { etag = value; } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HttpHeaders.parseSeconds(value, -1); } } } }
以前我写过一篇文章,是关于Okhttp中websocket的使用,因为webSocket属于长链接,因此须要进行监听,这里是用到了观察者模式:
final WebSocketListener listener; @Override public void onReadMessage(String text) throws IOException { listener.onMessage(this, text); }
这个就不举例了,每一个项目都会有
有一块儿学习的小伙伴能够关注下❤️个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。