深入理解OkHttp3:(七)事件(Events)

事件允许您捕获应用程序的HTTP请求的指标。并可以使用事件来监控:

  • 应用程序发出的HTTP请求的大小和频率。如果你的应用做了很多请求,或者你的Http请求很大,你应该知晓!
  • 这些网络请求在底层网络上的性能。如果网络的性能不够好,您需要改进网络或者减少使用网络。

EventListener

您感兴趣的事件的子类EventListener和覆盖方法。在没有重定向或重试的成功HTTP请求中,事件序列在下图中展示:

Events Diagram

下面的例子打印了事件和时间戳。

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.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 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 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 callEnd(Call call) {
      printEvent("callEnd");
    }

    @Override public void callFailed(Call call, IOException ioe) {
      printEvent("callFailed");
    }
  }
}

执行结果如下:

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

请注意,没有为第二个请求触发连接事件。为了显著提高性能,它重用了第一个请求的。

EventListener.Factory

在前面的示例中,我们使用callStartNanos字段来跟踪每个事件的运行时间。这很方便,但如果多个请求同时执行,就无法工作。为了适应这一点,使用工厂为每个请求创建一个新的EventListener实例。这允许每个侦听器保持特定的请求状态。

这个示例工厂为每个请求创建一个惟一的ID,并使用该ID区分日志消息中的请求。

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.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 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 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 callEnd(Call call) {
      printEvent("callEnd");
    }

    @Override public void callFailed(Call call, IOException ioe) {
      printEvent("callFailed");
    }
  }
}

执行结果如下:

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()。当失败发生时,开始事件可能没有相应的结束事件。

Events Diagram

事件重试和跟进

OkHttp具有快速恢复性,可以从一些连接失败中自动恢复。在本例中,connectFailed()事件不是最后事件,后面没有callFailed()。当尝试重试时,事件侦听器将接收多个相同类型的事件。

单个HTTP请求可能需要后续请求来处理身份验证授权、重定向和HTTP层次的超时。在这种情况下,可能会尝试多个连接、请求和响应。后续跟进是单个调用可能触发同一类型的多个事件的另一个原因。

Events Diagram

可用性

事件在OkHttp 3.11中作为公共API可用使用。未来的版本可能会引入新的事件类型;您需要重写相应的方法来处理它们。

本文为翻译文章,英文原文地址:https://github.com/square/okhttp/wiki/Events

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值