本文为翻译二次编辑文章,点击查看原文
OkHttp事件允许我们在应用发起http请求过程中捕获所有的请求指标,并可以通过事件来监听:
- 发起的http请求的大小和频次
当我们的请求过多或者请求数据过大的时候需要知道当前的指标信息。
- http请求在底层网络的性能表现
若网络的性能表现不足,则需要改善网络或较少http请求的使用,以避免不好的交互体验。
事件监听
子类EventListener和重写您感兴趣的事件的方法。在没有重定向或重试的成功HTTP调用中,此流程描述了事件的顺序。
以下是一个可以让每个时间都显示时间戳的时间监听器:
package okhttp3.recipes;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import okhttp3.Call;
import okhttp3.Connection;
import okhttp3.EventListener;
import okhttp3.Handshake;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
/**
* This prints events for a single in-flight call. It won't work for multiple concurrent calls
* because we don't know what callStartNanos refers to.
*/
public final class PrintEventsNonConcurrent {
private final OkHttpClient client = new OkHttpClient.Builder()
.eventListener(new PrintingEventListener())
.build();
// 我们通过如下代码发起几次请求
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
System.out.println("REQUEST 1 (new connection)");
try (Response response = client.newCall(request).execute()) {
// Consume and discard the response body.
response.body().source().readByteString();
}
System.out.println("REQUEST 2 (pooled connection)");
try (Response response = client.newCall(request).execute()) {
// Consume and discard the response body.
response.body().source().readByteString();
}
}
public static void main(String... args) throws Exception {
new PrintEventsNonConcurrent().run();
}
private static final class PrintingEventListener extends EventListener {
long callStartNanos;
private void printEvent(String name) {
long nowNanos = System.nanoTime();
if (name.equals("callStart")) {
callStartNanos = nowNanos;
}
long elapsedNanos = nowNanos - callStartNanos;
System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
}
@Override public void callStart(Call call) {
printEvent("callStart");
}
@Override public void proxySelectStart(Call call, HttpUrl url) {
printEvent("proxySelectStart");
}
@Override public void proxySelectEnd(Call call, HttpUrl url, List<Proxy> proxies) {
printEvent("proxySelectEnd");
}
@Override public void dnsStart(Call call, String domainName) {
printEvent("dnsStart");
}
@Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
printEvent("dnsEnd");
}
@Override public void connectStart(
Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
printEvent("connectStart");
}
@Override public void secureConnectStart(Call call) {
printEvent("secureConnectStart");
}
@Override public void secureConnectEnd(Call call, Handshake handshake) {
printEvent("secureConnectEnd");
}
@Override public void connectEnd(
Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
printEvent("connectEnd");
}
@Override public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy,
Protocol protocol, IOException ioe) {
printEvent("connectFailed");
}
@Override public void connectionAcquired(Call call, Connection connection) {
printEvent("connectionAcquired");
}
@Override public void connectionReleased(Call call, Connection connection) {
printEvent("connectionReleased");
}
@Override public void requestHeadersStart(Call call) {
printEvent("requestHeadersStart");
}
@Override public void requestHeadersEnd(Call call, Request request) {
printEvent("requestHeadersEnd");
}
@Override public void requestBodyStart(Call call) {
printEvent("requestBodyStart");
}
@Override public void requestBodyEnd(Call call, long byteCount) {
printEvent("requestBodyEnd");
}
@Override public void requestFailed(Call call, IOException ioe) {
printEvent("requestFailed");
}
@Override public void responseHeadersStart(Call call) {
printEvent("responseHeadersStart");
}
@Override public void responseHeadersEnd(Call call, Response response) {
printEvent("responseHeadersEnd");
}
@Override public void responseBodyStart(Call call) {
printEvent("responseBodyStart");
}
@Override public void responseBodyEnd(Call call, long byteCount) {
printEvent("responseBodyEnd");
}
@Override public void responseFailed(Call call, IOException ioe) {
printEvent("responseFailed");
}
@Override public void callEnd(Call call) {
printEvent("callEnd");
}
@Override public void callFailed(Call call, IOException ioe) {
printEvent("callFailed");
}
@Override public void canceled(Call call) {
printEvent("canceled");
}
}
}
如上,我们再main函数通过
new PrintEventsNonConcurrent().run();
发起多次请求,获得打印信息如下:
REQUEST 1 (new connection)
0.000 callStart
0.010 dnsStart
0.017 dnsEnd
0.025 connectStart
0.117 secureConnectStart
0.586 secureConnectEnd
0.586 connectEnd
0.587 connectionAcquired
0.588 requestHeadersStart
0.590 requestHeadersEnd
0.591 responseHeadersStart
0.675 responseHeadersEnd
0.676 responseBodyStart
0.679 responseBodyEnd
0.679 connectionReleased
0.680 callEnd
REQUEST 2 (pooled connection)
0.000 callStart
0.001 connectionAcquired
0.001 requestHeadersStart
0.001 requestHeadersEnd
0.002 responseHeadersStart
0.082 responseHeadersEnd
0.082 responseBodyStart
0.082 responseBodyEnd
0.083 connectionReleased
0.083 callEnd
注意,上述第二次请求并没有重新创建连接事件,它通过复用第一个连接,从而显著提高了访问效率。
工厂模式
在前面的示例中,我们使用了一个名为startStartNanos的字段来跟踪每个事件的经过时间。 这很方便,但是如果同时执行多个调用,它将不起作用。 为了适应这一点,请使用Factory为每个Call创建一个新的EventListener实例。 这允许每个侦听器保持特定于呼叫的状态。
该示例工厂为每个请求创建唯一的ID,并使用该ID区分日志消息中的请求。
package okhttp3.recipes;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Connection;
import okhttp3.EventListener;
import okhttp3.Handshake;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public final class PrintEvents {
private final OkHttpClient client = new OkHttpClient.Builder()
.eventListenerFactory(PrintingEventListener.FACTORY)
.build();
public void run() throws Exception {
Request washingtonPostRequest = new Request.Builder()
.url("https://www.washingtonpost.com/")
.build();
client.newCall(washingtonPostRequest).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
// Consume and discard the response body.
body.source().readByteString();
}
}
});
Request newYorkTimesRequest = new Request.Builder()
.url("https://www.nytimes.com/")
.build();
client.newCall(newYorkTimesRequest).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
// Consume and discard the response body.
body.source().readByteString();
}
}
});
}
public static void main(String... args) throws Exception {
new PrintEvents().run();
}
private static final class PrintingEventListener extends EventListener {
private static final Factory FACTORY = new Factory() {
final AtomicLong nextCallId = new AtomicLong(1L);
@Override
public EventListener create(Call call) {
long callId = nextCallId.getAndIncrement();
System.out.printf("%04d %s%n", callId, call.request().url());
return new PrintingEventListener(callId, System.nanoTime());
}
};
final long callId;
final long callStartNanos;
PrintingEventListener(long callId, long callStartNanos) {
this.callId = callId;
this.callStartNanos = callStartNanos;
}
private void printEvent(String name) {
long elapsedNanos = System.nanoTime() - callStartNanos;
System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name);
}
@Override
public void proxySelectStart(Call call, HttpUrl url) {
printEvent("proxySelectStart");
}
@Override
public void proxySelectEnd(Call call, HttpUrl url, List<Proxy> proxies) {
printEvent("proxySelectEnd");
}
@Override
public void callStart(Call call) {
printEvent("callStart");
}
@Override
public void dnsStart(Call call, String domainName) {
printEvent("dnsStart");
}
@Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
printEvent("dnsEnd");
}
@Override
public void connectStart(
Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
printEvent("connectStart");
}
@Override
public void secureConnectStart(Call call) {
printEvent("secureConnectStart");
}
@Override
public void secureConnectEnd(Call call, Handshake handshake) {
printEvent("secureConnectEnd");
}
@Override
public void connectEnd(
Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
printEvent("connectEnd");
}
@Override
public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy,
Protocol protocol, IOException ioe) {
printEvent("connectFailed");
}
@Override
public void connectionAcquired(Call call, Connection connection) {
printEvent("connectionAcquired");
}
@Override
public void connectionReleased(Call call, Connection connection) {
printEvent("connectionReleased");
}
@Override
public void requestHeadersStart(Call call) {
printEvent("requestHeadersStart");
}
@Override
public void requestHeadersEnd(Call call, Request request) {
printEvent("requestHeadersEnd");
}
@Override
public void requestBodyStart(Call call) {
printEvent("requestBodyStart");
}
@Override
public void requestBodyEnd(Call call, long byteCount) {
printEvent("requestBodyEnd");
}
@Override
public void requestFailed(Call call, IOException ioe) {
printEvent("requestFailed");
}
@Override
public void responseHeadersStart(Call call) {
printEvent("responseHeadersStart");
}
@Override
public void responseHeadersEnd(Call call, Response response) {
printEvent("responseHeadersEnd");
}
@Override
public void responseBodyStart(Call call) {
printEvent("responseBodyStart");
}
@Override
public void responseBodyEnd(Call call, long byteCount) {
printEvent("responseBodyEnd");
}
@Override
public void responseFailed(Call call, IOException ioe) {
printEvent("responseFailed");
}
@Override
public void callEnd(Call call) {
printEvent("callEnd");
}
@Override
public void callFailed(Call call, IOException ioe) {
printEvent("callFailed");
}
@Override
public void canceled(Call call) {
printEvent("canceled");
}
}
}
在加印wifi上测试比较两个请求的完成事件,002比001更快完成请求:
0001 https://www.washingtonpost.com/
0001 0.000 callStart
0002 https://www.nytimes.com/
0002 0.000 callStart
0002 0.010 dnsStart
0001 0.013 dnsStart
0001 0.022 dnsEnd
0002 0.019 dnsEnd
0001 0.028 connectStart
0002 0.025 connectStart
0002 0.072 secureConnectStart
0001 0.075 secureConnectStart
0001 0.386 secureConnectEnd
0002 0.390 secureConnectEnd
0002 0.400 connectEnd
0001 0.403 connectEnd
0002 0.401 connectionAcquired
0001 0.404 connectionAcquired
0001 0.406 requestHeadersStart
0002 0.403 requestHeadersStart
0001 0.414 requestHeadersEnd
0002 0.411 requestHeadersEnd
0002 0.412 responseHeadersStart
0001 0.415 responseHeadersStart
0002 0.474 responseHeadersEnd
0002 0.475 responseBodyStart
0001 0.554 responseHeadersEnd
0001 0.555 responseBodyStart
0002 0.554 responseBodyEnd
0002 0.554 connectionReleased
0002 0.554 callEnd
0001 0.624 responseBodyEnd
0001 0.624 connectionReleased
0001 0.624 callEnd
EventListener.Factory还可以将指标限制为调用的子集。 这是随机抽取10%的指标:
class MetricsEventListener extends EventListener {
private static final Factory FACTORY = new Factory() {
@Override public EventListener create(Call call) {
if (Math.random() < 0.10) {
return new MetricsEventListener(call);
} else {
return EventListener.NONE;
}
}
};
...
}
失败事件
当操作失败时,将调用失败方法。 这是connectFailed(),用于在建立与服务器的连接时失败;如果HTTP调用永久失败,则为callFailed()。 发生故障时,开始事件可能没有相应的结束事件。
重试和后续活动
OkHttp具有弹性,可以自动从某些连接故障中恢复。 在这种情况下,**connectFailed()事件不是终端事件,也不是callFailed()**之后的事件。 尝试重试时,事件侦听器将收到多个相同类型的事件。
单个HTTP调用可能需要发出后续请求,以处理身份验证质询,重定向和HTTP层超时。 在这种情况下,可能会尝试多个连接,请求和响应。 跟踪是单个调用可能触发相同类型的多个事件的另一个原因。
可用性
在OkHttp 3.11中,事件可以作为公共API使用。 将来的版本可能会引入新的事件类型; 您将需要重写相应的方法来处理它们。