本章节讲述OkHttp框架可以操作的网络优化
一.EventListener类 OkHttp网络请求耗时统计
1.EventListener类继承类
/**
* EventListener监听实现类
*/
public class OkHttpEventListener extends EventListener {
/**
* 请求开始
*/
private long mCallStartTime;
private long mDnsStartTime;
private long mConnectStartTime;
private long mSecureConnectStartTime;
private long mConnectionStartTime;
private long mRequestHeadersStartTime;
private long mRequestBodyStartTime;
private long mResponseHeadersStartTime;
private long mResponseBodyStartTime;
/**
* 请求开始
*/
@Override
public void callStart(Call call) {
super.callStart(call);
mCallStartTime = System.nanoTime();
}
/**
* 请求正常结束
*/
@Override
public void callEnd(Call call) {
super.callEnd(call);
logMethod(call.request().toString() + "方法的 callEnd", mCallStartTime);
}
/**
* 请求异常结束
*/
@Override
public void callFailed(Call call, IOException ioe) {
super.callFailed(call, ioe);
logMethod(call.request().toString() + "方法的 callFailed", mCallStartTime);
}
/**
* dns解析开始
* DNS解析是请求DNS(Domain Name System)服务器。将域名解析成ip的过程。
*/
@Override
public void dnsStart(Call call, String domainName) {
super.dnsStart(call, domainName);
mDnsStartTime = System.nanoTime();
}
/**
* dns解析结束
*/
@Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
super.dnsEnd(call, domainName, inetAddressList);
logMethod(call.request().toString() + "方法的 dnsEnd", mDnsStartTime);
}
/**
* 连接开始
*/
@Override
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
super.connectStart(call, inetSocketAddress, proxy);
mConnectStartTime = System.nanoTime();
}
/**
* 连接结束
*/
@Override
public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
super.connectEnd(call, inetSocketAddress, proxy, protocol);
logMethod(call.request().toString() + "方法的 connectEnd", mConnectStartTime);
}
/**
* 连接失败
*/
@Override
public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe) {
super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
logMethod(call.request().toString() + "方法的 connectFailed", mConnectStartTime);
}
/**
* TLS安全连接开始
*/
@Override
public void secureConnectStart(Call call) {
super.secureConnectStart(call);
mSecureConnectStartTime = System.nanoTime();
}
/**
* TLS安全连接结束
*/
@Override
public void secureConnectEnd(Call call, Handshake handshake) {
super.secureConnectEnd(call, handshake);
logMethod(call.request().toString() + "方法的 secureConnectEnd", mSecureConnectStartTime);
}
/**
* 连接绑定
*/
@Override
public void connectionAcquired(Call call, Connection connection) {
super.connectionAcquired(call, connection);
mConnectionStartTime = System.nanoTime();
}
/**
* 连接释放
*/
@Override
public void connectionReleased(Call call, Connection connection) {
super.connectionReleased(call, connection);
logMethod(call.request().toString() + "方法的 connectionReleased", mConnectionStartTime);
}
/**
* 请求Header开始
*/
@Override
public void requestHeadersStart(Call call) {
super.requestHeadersStart(call);
mRequestHeadersStartTime = System.nanoTime();
}
/**
* 请求Header结束
*/
@Override
public void requestHeadersEnd(Call call, Request request) {
super.requestHeadersEnd(call, request);
logMethod(call.request().toString() + "方法的 requestHeadersEnd", mRequestHeadersStartTime);
}
/**
* 请求Body开始
*/
@Override
public void requestBodyStart(Call call) {
super.requestBodyStart(call);
mRequestBodyStartTime = System.nanoTime();
}
/**
* 请求Body结束
*/
@Override
public void requestBodyEnd(Call call, long byteCount) {
super.requestBodyEnd(call, byteCount);
logMethod(call.request().toString() + "方法的 requestBodyEnd", mRequestBodyStartTime);
}
/**
* 响应Header开始
*/
@Override
public void responseHeadersStart(Call call) {
super.responseHeadersStart(call);
mResponseHeadersStartTime = System.nanoTime();
}
/**
* 响应Header结束
*/
@Override
public void responseHeadersEnd(Call call, Response response) {
super.responseHeadersEnd(call, response);
logMethod(call.request().toString() + "方法的 responseHeadersEnd", mResponseHeadersStartTime);
}
/**
* 响应Body开始
*/
@Override
public void responseBodyStart(Call call) {
super.responseBodyStart(call);
mResponseBodyStartTime = System.nanoTime();
}
/**
* 响应Body结束
*/
@Override
public void responseBodyEnd(Call call, long byteCount) {
super.responseBodyEnd(call, byteCount);
logMethod(call.request().toString() + "方法的 responseBodyEnd", mResponseBodyStartTime);
}
/**
* 打印公共方法
*/
private void logMethod(String name, long startTime) {
long time = System.nanoTime() - startTime;
Log.d("OkHttpOptimizeActivity", name + "方法 耗时:" + (time / 1000000000d) + "秒");
}
}
2.添加eventListener监听
public class OkHttpOptimizeActivity extends AppCompatActivity {
private String getUrl = "http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue";
private String postUrl = "http://fanyi.youdao.com/openapi.do";
private OkHttpClient mOkHttpClient = null;//OkHttpClient 对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_okhttp_optimize);
//创建OkHttpClient对象
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)//连接时间
.readTimeout(20, TimeUnit.SECONDS)//读时间
.writeTimeout(20, TimeUnit.SECONDS)//写时间
.retryOnConnectionFailure(true)//连接失败后是否重新连接
.dns(new OkHttpDNS())//DNS域名解析
.eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
.build();
findViewById(R.id.activity_okhttp_optimize_textview1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Get请求测试
getData();
//Post请求测试
Map<String, String> params = new HashMap<>();
params.put("keyfrom", "imoocdict123456");
params.put("key", "324273592");
params.put("type", "data");
params.put("doctype", "json");
params.put("version", "1.1");
params.put("q", "red");
postData(params);
}
});
}
/**
* OkHttp Get请求
*/
private void getData() {
//1.创建Request对象 构建一个具体的网络请求对象 包括具体的请求url&请求头&请求体等等 tag设置每个接口的tag 用于区分那个方法
Request request = new Request.Builder().url(getUrl).get().tag("FA0010").build();
//2.获取Call对象 将具体的网络请求与执行请求的实体进行绑定,形成一个具体的正式的可执行实体
Call call = mOkHttpClient.newCall(request);
//3.Call对象进行网络请求 enqueue异步 execute同步一般不用
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("OkHttpOptimizeActivity", "Get请求 onFailure方法执行 错误信息----:" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.d("OkHttpOptimizeActivity", "Get请求 onResponse方法执行报文----:" + response.body().string());
}
}
});
}
/**
* OkHttp Post请求
*/
private void postData(final Map<String, String> params) {
//1.获取FormBody.Builder对象
FormBody.Builder builder = new FormBody.Builder();
//2.遍历params 将其放入到FormBody.Builder 对象
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
}
//3.获取RequestBody 对象
RequestBody requestBody = builder.build();
//4.创建Request对象 构建一个具体的网络请求对象 包括具体的请求url&请求头&请求体等等 tag设置每个接口的tag 用于区分那个方法
Request request = new Request.Builder().url(postUrl).post(requestBody).tag("FA0011").build();
//5.获取Call对象 将具体的网络请求与执行请求的实体进行绑定,形成一个具体的正式的可执行实体
Call call = mOkHttpClient.newCall(request);
//6.Call对象进行网络请求 enqueue异步 execute同步一般不用
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("OkHttpOptimizeActivity", "Post请求 onFailure方法执行 错误信息----:" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.d("OkHttpOptimizeActivity", "Post请求 onResponse方法执行报文----:" + response.body().string());
}
}
});
}
}
3.结果
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 dnsEnd方法 耗时:0.004301719秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 dnsEnd方法 耗时:0.004300885秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 connectEnd方法 耗时:0.044430729秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 connectEnd方法 耗时:0.04443151秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 requestHeadersEnd方法 耗时:6.17344E-4秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 requestHeadersEnd方法 耗时:6.71146E-4秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 requestBodyEnd方法 耗时:7.63646E-4秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 responseHeadersEnd方法 耗时:0.093491979秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 responseBodyEnd方法 耗时:0.003987135秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 connectionReleased方法 耗时:0.102222969秒
Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 callEnd方法 耗时:0.159830573秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 responseHeadersEnd方法 耗时:0.163656458秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 responseBodyEnd方法 耗时:0.001606042秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 connectionReleased方法 耗时:0.170009584秒
Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 callEnd方法 耗时:0.227597239秒
4.说明
<1> OkHttp框架提供了EventListener抽象类,可以方便的让开发者获取各种网络时间,例如DNS解析、TSL/SSL连接、Response接收等。
<2> EventListener抽象类中重写的方法,每个方法都有一个Call对象。可以使用这个对象获取网络请求的很多参数。
比如 获取 Request request() 从而获取 等等
final HttpUrl url;
final String method;
final Headers headers;
二.DNS优化
1.简介
DNS(Domain Name System),它的作用就是 根据域名 查出 对应的IP地址。它是 HTTP 协议的前提。只有将域名正确的解析成 IP 地址后,后面的 HTTP 流程才可以继续进行下去。
在咱们的App访问网络的时候。DNS解析是网络请求的第一步。默认我们使用运营商的LocalDNS服务。3G 网络下,耗时在 200~300ms左右。4G 网络下需要 100ms左右。
解析慢 并不 LocalDNS最大的问题,它还存在一些更为严重的问题。例如:DNS 劫持、DNS 调度不准确(缓存、转发、NAT)导致性能退化等等,这些才是网络优化最应该解决的问题。
想要优化 DNS,现在最简单成熟的方案,就是使用 HTTPDNS。
DNS和HTTPDNS区别
DNS:不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。
HTTPDNS:顾名思义它是利用 HTTP协议与 DNS服务器的80端口进行交互。不走传统的 DNS 解析,从而绕过运营商的LocalDNS服务器,有效的防止了域名劫持,提高域名解析的效率。
2.代码实现
OKHttp框架实现DNS转换成HTTPDNS。有两种方式。
<1> 拦截器实现
略
<2> Dns接口实现
OkHttp提供了Dns接口专门用来实现DNS优化
接口实现类
public class OkHttpDNS implements Dns {
private String mIpString;
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
Log.d("OkHttpDNS", "hostname----:" + hostname);
if (TextUtils.isEmpty(hostname) || null == hostname) return SYSTEM.lookup(hostname);
//根据域名获取IP
InetAddress[] ips = InetAddress.getAllByName(hostname);
for (InetAddress inetAddress : ips) {
mIpString = inetAddress.getHostAddress();
Log.d("OkHttpDNS", "mIpString----:" + mIpString);
}
if (null != mIpString && !TextUtils.isEmpty(mIpString)) {
return Arrays.asList(InetAddress.getAllByName(mIpString));
} else {
return SYSTEM.lookup(hostname);
}
}
}
添加接口
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)//连接时间
.readTimeout(20, TimeUnit.SECONDS)//读时间
.writeTimeout(20, TimeUnit.SECONDS)//写时间
.retryOnConnectionFailure(true)//连接失败后是否重新连接
.dns(new OkHttpDNS())//DNS域名解析
.eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
.build();
三.gzip 压缩
1.简介
我们知道,在HTTP传输时是支持 gzip 压缩的,客户端发起请求时在请求头里增加 Accept-Encoding: gzip,服务端响应时在返回的头信息里增加 Content-Encoding: gzip,这表示传输的数据是采用 gzip 压缩的。默认情况下,传输内容是不压缩的,采用 gzip 压缩后可以大幅减少传输内容大小,这样可以提高传输速度,减少流量的使用。
OkHttp框架是默认支持GZip压缩的。
和后台商量好后,可以自定义GZip压缩。
2.客户端实现
<1> Interceptor拦截器接口实现类
public class OkHttpGZip implements Interceptor {
@NonNull
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
/**
* 自定义GZip压缩
*/
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}
@Override
public long contentLength() {
return -1;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
<2> 添加拦截器
//创建OkHttpClient对象
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)//连接时间
.readTimeout(20, TimeUnit.SECONDS)//读时间
.writeTimeout(20, TimeUnit.SECONDS)//写时间
.retryOnConnectionFailure(true)//连接失败后是否重新连接
.dns(new OkHttpDNS())//DNS域名解析
.eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
.hostnameVerifier(new OkHttpHostnameVerifier())//验签
.addInterceptor(new OkHttpGZip())//添加拦截器
.build();
<3> 说明
自定义GZip一定要和后台商议好,否则如果后台忽然有一天修改了压缩问题。就会导致客户端接口请求异常。
<4> 原因
因为OkHttp默认已经实现了GZip。在一个叫BridgeInterceptor类里面。
OKHttp框架的BridgeInterceptor源码
public final class BridgeInterceptor implements Interceptor {
private final CookieJar cookieJar;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
/** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
}
添加
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
使用
if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
...
String contentType = networkResponse.header("Content-Type");
...
}
所以,如果没有特殊需求或者没有和后台商量好的情况下,最好不要自定义使用OkHttp框架的GZip。
四.连接池复用
简介
提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。
比如我们在浏览器中发起请求的时候,header部分会有keep-alive字段。keep-alive就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。
那么OkHttp的连接池是怎么复用的呢?我们按照OkHttp3.X的源码看一下。
OkHttp源码中有一个类ConnectionPool。顾名思义,看名字就知道这个类就是OkHttp的连接池。
ConnectionPool源码
public final class ConnectionPool {
private static final Executor executor;
private final int maxIdleConnections;
private final long keepAliveDurationNs;
private final Runnable cleanupRunnable;
private final Deque<RealConnection> connections;
final RouteDatabase routeDatabase;
boolean cleanupRunning;
public ConnectionPool() {
this(5, 5L, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.cleanupRunnable = new Runnable() {
public void run() {
while(true) {
long waitNanos = ConnectionPool.this.cleanup(System.nanoTime());
if (waitNanos == -1L) {
return;
}
if (waitNanos > 0L) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= waitMillis * 1000000L;
synchronized(ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int)waitNanos);
} catch (InterruptedException var8) {
}
}
}
}
}
};
this.connections = new ArrayDeque();
this.routeDatabase = new RouteDatabase();
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
if (keepAliveDuration <= 0L) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
public synchronized int idleConnectionCount() {
int total = 0;
Iterator var2 = this.connections.iterator();
while(var2.hasNext()) {
RealConnection connection = (RealConnection)var2.next();
if (connection.allocations.isEmpty()) {
++total;
}
}
return total;
}
public synchronized int connectionCount() {
return this.connections.size();
}
@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert Thread.holdsLock(this);
Iterator var4 = this.connections.iterator();
RealConnection connection;
do {
if (!var4.hasNext()) {
return null;
}
connection = (RealConnection)var4.next();
} while(!connection.isEligible(address, route));
streamAllocation.acquire(connection, true);
return connection;
}
@Nullable
Socket deduplicate(Address address, StreamAllocation streamAllocation) {
assert Thread.holdsLock(this);
Iterator var3 = this.connections.iterator();
RealConnection connection;
do {
if (!var3.hasNext()) {
return null;
}
connection = (RealConnection)var3.next();
} while(!connection.isEligible(address, (Route)null) || !connection.isMultiplexed() || connection == streamAllocation.connection());
return streamAllocation.releaseAndAcquire(connection);
}
void put(RealConnection connection) {
assert Thread.holdsLock(this);
if (!this.cleanupRunning) {
this.cleanupRunning = true;
executor.execute(this.cleanupRunnable);
}
this.connections.add(connection);
}
boolean connectionBecameIdle(RealConnection connection) {
assert Thread.holdsLock(this);
if (!connection.noNewStreams && this.maxIdleConnections != 0) {
this.notifyAll();
return false;
} else {
this.connections.remove(connection);
return true;
}
}
public void evictAll() {
List<RealConnection> evictedConnections = new ArrayList();
synchronized(this) {
Iterator i = this.connections.iterator();
while(true) {
if (!i.hasNext()) {
break;
}
RealConnection connection = (RealConnection)i.next();
if (connection.allocations.isEmpty()) {
connection.noNewStreams = true;
evictedConnections.add(connection);
i.remove();
}
}
}
Iterator var2 = evictedConnections.iterator();
while(var2.hasNext()) {
RealConnection connection = (RealConnection)var2.next();
Util.closeQuietly(connection.socket());
}
}
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = -9223372036854775808L;
synchronized(this) {
Iterator i = this.connections.iterator();
while(i.hasNext()) {
RealConnection connection = (RealConnection)i.next();
if (this.pruneAndGetAllocationCount(connection, now) > 0) {
++inUseConnectionCount;
} else {
++idleConnectionCount;
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
}
if (longestIdleDurationNs < this.keepAliveDurationNs && idleConnectionCount <= this.maxIdleConnections) {
if (idleConnectionCount > 0) {
return this.keepAliveDurationNs - longestIdleDurationNs;
}
if (inUseConnectionCount > 0) {
return this.keepAliveDurationNs;
}
this.cleanupRunning = false;
return -1L;
}
this.connections.remove(longestIdleConnection);
}
Util.closeQuietly(longestIdleConnection.socket());
return 0L;
}
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
int i = 0;
while(i < references.size()) {
Reference<StreamAllocation> reference = (Reference)references.get(i);
if (reference.get() != null) {
++i;
} else {
StreamAllocationReference streamAllocRef = (StreamAllocationReference)reference;
String message = "A connection to " + connection.route().address().url() + " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
references.remove(i);
connection.noNewStreams = true;
if (references.isEmpty()) {
connection.idleAtNanos = now - this.keepAliveDurationNs;
return 0;
}
}
}
return references.size();
}
static {
executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
}
}
源码讲解
<1> 全局变量
//线程池,核心线程数为0,最大线程数为最大整数,线程空闲存活时间60s。SynchronousQueue 直接提交策略
private static final Executor executor;
//空闲连接的最大连接数
private final int maxIdleConnections;
//保持连接的周期
private final long keepAliveDurationNs;
//Runable
private final Runnable cleanupRunnable;
//双端队列,存放具体的连接
private final Deque<RealConnection> connections;
//用于记录连接失败的route
final RouteDatabase routeDatabase;
<2> 创建了一个线程池
static {
executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
}
使用这个线程池,来异步执行如下的put操作。
<3> put操作
void put(RealConnection connection) {
assert Thread.holdsLock(this);
if (!this.cleanupRunning) {
this.cleanupRunning = true;
executor.execute(this.cleanupRunnable);
}
this.connections.add(connection);
}
完成两项内容
(1) 线程池异步执行put操作。
(2) 将连接对象RealConnection存放到Deque<RealConnection>集合。
<4> get操作
@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert Thread.holdsLock(this);
Iterator var4 = this.connections.iterator();
RealConnection connection;
do {
if (!var4.hasNext()) {
return null;
}
connection = (RealConnection)var4.next();
} while(!connection.isEligible(address, route));
streamAllocation.acquire(connection, true);
return connection;
}
get方法中对存放具体连接的双端队列connections进行遍历,如果连接有效,则利用acquire()计数。
<5> 计数操作
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert Thread.holdsLock(this.connectionPool);
if (this.connection != null) {
throw new IllegalStateException();
} else {
this.connection = connection;
this.reportedAcquired = reportedAcquired;
connection.allocations.add(new StreamAllocation.StreamAllocationReference(this, this.callStackTrace));
}
}
使用StreamAllocation完成计数。
<6> 清空计数操作
private void release(RealConnection connection) {
int i = 0;
for(int size = connection.allocations.size(); i < size; ++i) {
Reference<StreamAllocation> reference = (Reference)connection.allocations.get(i);
if (reference.get() == this) {
connection.allocations.remove(i);
return;
}
}
throw new IllegalStateException();
}
使用StreamAllocation完成清空计数。StreamAllocation使用的软引用。
总结
OkHttp3连接池的复用主要是对双端队列Deque<RealConnection>进行操作,通过对StreamAllocation的引用计数实现自动回收。