1. 用法: 设置超时时间
OkHttpClient httpClient = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) //连接超时
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) //读取超时
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) //写超时
.addInterceptor(getHeaderInterceptor())
.addInterceptor(new CacheInterceptor())
.addInterceptor(new HttpLoggerInterceptor()
.setLevel(BuildConfig.DEBUG ? Level.BODY : Level.NONE)
.setTag(HTTP_LOG_TAG))
.build();
这个都知道, 一搜一大把, 但是没人讲这三种timeout有什么区别...
2. 总结
源码分析之前先上总结
- connectTimeout 最终设置给了socket (确切的说应该是rawSocket)
- readTimeout 最终设置给了rawSocket 以及 在socket基础上创建的BufferedSource
- writeTimeout 最终设置给了在socket基础上创建的BufferedSink
一言以蔽之: okhttp底层基于socket, 所以 Timeout 自然也是设置给�Socket 的 connect / read / write
当然, 不懂socket怎么用的最好先查一下~
以下的源码探究就是罗列记录以下自己的探究过程, 可以忽略~
3. 源码探究
3.1 设置给rawSocket 上的 connectTimeout 和 readTimeout
具体实现在 RealConnection
这个类的connectSocket(,,,)
方法
/**
* Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket.
*/
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//在未设置proxy的情况下, 会采用默认的proxySelector, 此时的proxy.type == DIRECT 即直连
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket() // 走这里, 实际new Socket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
//最终调用socket.setSoTimeout方法, 设置读取server端数据的超时;
rawSocket.setSoTimeout(readTimeout);
try {
//实际调用的是 rawSocket.connect(route.socketAddress(), connectTimeout), 设置连接server的超时时长
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
...
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
//创建source
source = Okio.buffer(Okio.source(rawSocket));
//创建sink
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
关于socket.setSoTimeout
, 以下是原文档说明的个人翻译及理解
调用此方法设置一个非0的timeout,那么调用InputStream(与此Socket相关联的) 的read()这个阻塞方法读取server端的数据时, 持续timeout之久。
如果timeout 到期,不管Socket是否有效, 都会抛出java.net.SocketTimeoutException。
这个timeout 必须在socket进入block操作之前设置 才能生效;
正常设置timeout >0, 如果设置timeout=0, 则代表 timeout无限;
关于socket.connect(address, connectTimeout);
Connects this socket to the server with a specified timeout value. A timeout of zero is interpreted as an infinite timeout. The connection will then block until established or an error occurs.
简言之就是 与server建立连接的最大时长
3.2 BufferedSource上的 readTimeout 和 BufferedSink上的writeTimeout
具体实现在RealConnection
的newCodec方法
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
//此处又给socket设置了一次readTimeout, 当然此socket已经不一定是rawSocket了
socket.setSoTimeout(chain.readTimeoutMillis());
//
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
//
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
当然还有一个地方是在connectTunnel()
用到, 但是这个前提是走http代理的时候, 这个暂且不详细探究;
3.3 下面是source和sink中的timeout 的详细解释
Source 和 Sink 是 okio 中定义的两个接口, 这两个接口都支持读写超时设置
其中source可以理解为inputstream, sink可以理解为outputstream
具体是什么鬼, 看一下source和sink的创建就是知道了
BufferedSource的创建
罗列细节之前先总结一下流程:
Socket ----> InputStream ---> Source ---> BufferedSource
还是RealConnection的connectSocket方法
//创建BufferedSource
source = Okio.buffer(Okio.source(rawSocket));
Okio.buffer(Source source)
就是new RealBufferedSource(source);
那么下面主要来看Okio.source(rawSocket)
public static Source source(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
//此处用socket的inputstream创建了source
Source source = source(socket.getInputStream(), timeout);
return timeout.source(source);
}
//下面请看 okio 是如何将 inputstream 封装成 source 的
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
//每次read都会检测timeout
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
//本质还是调用了inputstream的read方法
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}
BufferedSink的创建
跟BuffedSource很相似, 简略描述
sink = Okio.buffer(Okio.sink(rawSocket));
同样主要看Okio.sink(rawSocket)
的实现
public static Sink sink(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
//用socket的outputstream创建sink
Sink sink = sink(socket.getOutputStream(), timeout);
return timeout.sink(sink);
}
sink静态方法的实现
private static Sink sink(final OutputStream out, final Timeout timeout) {
if (out == null) throw new IllegalArgumentException("out == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Sink() {
@Override public void write(Buffer source, long byteCount) throws IOException {
checkOffsetAndCount(source.size, 0, byteCount);
while (byteCount > 0) {
//每次write之前检测timeout
timeout.throwIfReached();
Segment head = source.head;
int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
//最终调用outputstream的write方法
out.write(head.data, head.pos, toCopy);
head.pos += toCopy;
byteCount -= toCopy;
source.size -= toCopy;
if (head.pos == head.limit) {
source.head = head.pop();
SegmentPool.recycle(head);
}
}
}
@Override public void flush() throws IOException {
out.flush();
}
@Override public void close() throws IOException {
out.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "sink(" + out + ")";
}
};
}
以上~