胖蔡说技术
随便扯扯

OkHttp 事件(Events)

本文为翻译二次编辑文章,点击查看原文

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使用。 将来的版本可能会引入新的事件类型; 您将需要重写相应的方法来处理它们。

赞(0) 打赏
转载请附上原文出处链接:胖蔡说技术 » OkHttp 事件(Events)
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏