雲計算

OkHttp請求耗時統計

目錄介紹

  • 01.先提問一個問題
  • 02.EventListener回調原理
  • 03.請求開始結束監聽
  • 04.dns解析開始結束監聽
  • 05.連接開始結束監聽
  • 06.TLS連接開始結束監聽
  • 07.連接綁定和釋放監聽
  • 08.request請求監聽
  • 09.response響應監聽
  • 10.如何監聽統計耗時
  • 11.應用實踐之案例

01.先提問一個問題

  • OkHttp如何進行各個請求環節的耗時統計呢?

    • OkHttp 版本提供了EventListener接口,可以讓調用者接收一系列網絡請求過程中的事件,例如DNS解析、TSL/SSL連接、Response接收等。
    • 通過繼承此接口,調用者可以監視整個應用中網絡請求次數、流量大小、耗時(比如dns解析時間,請求時間,響應時間等等)情況。

02.EventListener回調原理

  • 先來看一下

    public abstract class EventListener {
       // 按照請求順序回調
        public void callStart(Call call) {}
        // 域名解析
        public void dnsStart(Call call, String domainName) {}
        public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {}
        // 釋放當前Transmitter的RealConnection
        public void connectionReleased(Call call, Connection connection) {}
        public void connectionAcquired(call, result){};
        // 開始連接
        public void connectStart(call, route.socketAddress(), proxy){}
        // 請求
        public void requestHeadersStart(@NotNull Call call){}
        public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) {}
        // 響應
        public void requestBodyStart(@NotNull Call call) {}
        public void requestBodyEnd(@NotNull Call call, long byteCount) {}
        // 結束
        public void callEnd(Call call) {}
        // 失敗
        public void callFailed(Call call, IOException ioe) {}
    }

03.請求開始結束監聽

  • callStart(Call call) 請求開始

    • 當一個Call(代表一個請求)被同步執行或被添加異步隊列中時,即會調用這個回調方法。
    • 需要說明這個方法是在dispatcher.executed/enqueue前執行的。
    • 由於線程或事件流的限制,這裡的請求開始並不是真正的去執行的這個請求。如果發生重定向和多域名重試時,這個方法也僅被調用一次。
    final class RealCall implements Call {
        @Override 
        public Response execute() throws IOException {
            eventListener.callStart(this);
            client.dispatcher().executed(this);
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;    
        }
    
        @Override 
        public void enqueue(Callback responseCallback) {
            eventListener.callStart(this);
            client.dispatcher().enqueue(new AsyncCall(responseCallback));
        }
    }
  • callFailed/callEnd 請求異常和請求結束

    • 每一個callStart都對應著一個callFailed或callEnd。
    • callFailed在兩種情況下被調用,第一種是在請求執行的過程中發生異常時。第二種是在請求結束後,關閉輸入流時產生異常時。
    final class RealCall implements Call {
        @Override 
        public Response execute() throws IOException {
            try {
              client.dispatcher().executed(this);
              Response result = getResponseWithInterceptorChain();
              if (result == null) throw new IOException("Canceled");
              return result;
            } catch (IOException e) {
              eventListener.callFailed(this, e);
              throw e;
            }
        }
        final class AsyncCall extends NamedRunnable {
            @Override 
            protected void execute() {
                try {
                    Response response = getResponseWithInterceptorChain();
                } catch (IOException e) {
                    eventListener.callFailed(RealCall.this, e);
    
                }
            }
        }
    }
    
    //第二種
    public final class StreamAllocation {
        public void streamFinished(boolean noNewStreams, HttpCodec codec, long bytesRead, IOException e) {
            ...
            if (e != null) {
              eventListener.callFailed(call, e);
            } else if (callEnd) {
              eventListener.callEnd(call);
            }
            ...
        }
    }
    • callEnd也有兩種調用場景。第一種也是在關閉流時。第二種是在釋放連接時。
    public final class StreamAllocation {
    
        public void streamFinished(boolean noNewStreams, HttpCodec codec, long bytesRead, IOException e) {
            ...
            if (e != null) {
              eventListener.callFailed(call, e);
            } else if (callEnd) {
              eventListener.callEnd(call);
            }
            ...
        }
    
        public void release() {
            ...
            if (releasedConnection != null) {
              eventListener.connectionReleased(call, releasedConnection);
              eventListener.callEnd(call);
            }
        }
    }
    • 為什麼會將關閉流和關閉連接區分開?

      • 在http2版本中,一個連接上允許打開多個流,OkHttp使用StreamAllocation來作為流和連接的橋樑。當一個流被關閉時,要檢查這條連接上還有沒有其他流,如果沒有其他流了,則可以將連接關閉了。
      • streamFinished和release作用是一樣的,都是關閉當前流,並檢查是否需要關閉連接。不同的是,當調用者手動取消請求時,調用的是release方法,並由調用者負責關閉請求輸出流和響應輸入流。

04.dns解析開始結束監聽

  • dnsStart開始

    • 其中的lookup(String hostname)方法代表了域名解析的過程,dnsStart/dnsEnd就是在lookup前後被調用的
    • DNS解析是請求DNS(Domain Name System)服務器,將域名解析成ip的過程。域名解析工作是由JDK中的InetAddress類完成的。
      /** Prepares the socket addresses to attempt for the current proxy or host. */
      private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
        if (proxy.type() == Proxy.Type.SOCKS) {
          inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
        } else {
          eventListener.dnsStart(call, socketHost);
    
          // Try each address for best behavior in mixed IPv4/IPv6 environments.
          List<InetAddress> addresses = address.dns().lookup(socketHost);
          if (addresses.isEmpty()) {
            throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
          }
    
          eventListener.dnsEnd(call, socketHost, addresses);
        }
      }
  • 那麼RouteSelector這個類是在哪裡調用

    public final class StreamAllocation {
    
      public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
          EventListener eventListener, Object callStackTrace) {
        this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
      }
    }

05.連接開始結束監聽

  • connectStart連接開始

    • OkHttp是使用Socket接口建立Tcp連接的,所以這裡的連接就是指Socket建立一個連接的過程。
    • 當連接被重用時,connectStart/connectEnd不會被調用。當請求被重定向到新的域名後,connectStart/connectEnd會被調用多次。
      private void connectSocket(int connectTimeout, int readTimeout, Call call,
          EventListener eventListener) throws IOException {
        Proxy proxy = route.proxy();
        Address address = route.address();
    
        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()
            : new Socket(proxy);
    
        eventListener.connectStart(call, route.socketAddress(), proxy);
      }
  • connectEnd連接結束

    • 因為創建的連接有兩種類型(服務端直連和隧道代理),所以callEnd有兩處調用位置。為了在基於代理的連接上使用SSL,需要單獨發送CONECT請求。
    • 在連接過程中,無論是Socket連接失敗,還是TSL/SSL握手失敗,都會回調connectEnd。
      public void connect(int connectTimeout, int readTimeout, int writeTimeout,
        while (true) {
          try {
            establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
            eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
            break;
          } catch (IOException e) {
            eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);
          }
      }
    
      private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
          EventListener eventListener) throws IOException {
        Request tunnelRequest = createTunnelRequest();
        HttpUrl url = tunnelRequest.url();
        for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
          connectSocket(connectTimeout, readTimeout, call, eventListener);
          eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
        }
      }

06.TLS連接開始結束監聽

  • 開始連接,代碼如下所示

    • 在上面看到,在Socket建立連接後,會執行一個establishProtocol方法,這個方法的作用就是TSL/SSL握手。
    • 當存在重定向或連接重試的情況下,secureConnectStart/secureConnectEnd會被調用多次。
      private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
          int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
        if (route.address().sslSocketFactory() == null) {
          protocol = Protocol.HTTP_1_1;
          socket = rawSocket;
          return;
        }
    
        eventListener.secureConnectStart(call);
        connectTls(connectionSpecSelector);
        eventListener.secureConnectEnd(call, handshake);
      }
  • 結合連接監聽可知

    • 如果我們使用了HTTPS安全連接,在TCP連接成功後需要進行TLS安全協議通信,等TLS通訊結束後才能算是整個連接過程的結束,也就是說connectEnd在secureConnectEnd之後調用。
  • 所以順序是這樣的

    • connectStart ---> secureConnectStart ---> secureConnectEnd ---> ConnectEnd

07.連接綁定和釋放監聽

  • 因為OkHttp是基於連接複用的,當一次請求結束後並不會馬上關閉當前連接,而是放到連接池中。

    • 當有相同域名的請求時,會從連接池中取出對應的連接使用,減少了連接的頻繁創建和銷燬。
    • 當根據一個請求從連接池取連接時,並打開輸入輸出流就是acquired,用完釋放流就是released。
    • 如果直接複用StreamAllocation中的連接,則不會調用connectionAcquired/connectReleased。
      private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        synchronized (connectionPool) {
          if (result == null) {
            // 第一次查緩存 Attempt to get a connection from the pool.
            // Attempt to get a connection from the pool.
            Internal.instance.get(connectionPool, address, this, null);
          }
        }
    
        if (releasedConnection != null) {
          eventListener.connectionReleased(call, releasedConnection);
        }
        if (foundPooledConnection) {
          eventListener.connectionAcquired(call, result);
        }
    
        synchronized (connectionPool) {
          if (canceled) throw new IOException("Canceled");
    
          if (newRouteSelection) {
            //第二次查緩存
            List<Route> routes = routeSelection.getAll();
            for (int i = 0, size = routes.size(); i < size; i++) {
              Route route = routes.get(i);
              Internal.instance.get(connectionPool, address, this, route);
              if (connection != null) {
                foundPooledConnection = true;
                result = connection;
                this.route = route;
                break;
              }
            }
          }
    
          if (!foundPooledConnection) {
            //如果緩存沒有,則新建連接
            route = selectedRoute;
            refusedStreamCount = 0;
            result = new RealConnection(connectionPool, selectedRoute);
            acquire(result, false);
          }
        }
    
        // If we found a pooled connection on the 2nd time around, we're done.
        if (foundPooledConnection) {
          eventListener.connectionAcquired(call, result);
          return result;
        }
    
        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);
        routeDatabase().connected(result.route());
    
        eventListener.connectionAcquired(call, result);
        return result;
      }
  • connectionAcquired是在連接成功後被調用的。

    • 但是在連接複用的情況下沒有連接步驟,connectAcquired會在獲取緩存連接後被調用。由於StreamAllocation是連接“Stream”和“Connection”的橋樑,所以在StreamAllocation中會持有一個RealConnection引用。StreamAllocation在查找可用連接的順序為:StreamAllocation.RealConnection -> ConnectionPool -> ConnectionPool -> new RealConnection

08.request請求監聽

  • 在OkHttp中,HttpCodec負責對請求和響應按照Http協議進行編解碼,包含發送請求頭、發送請求體、讀取響應頭、讀取響應體。
  • requestHeaders開始和結束,這個直接看CallServerInterceptor攔截器代碼即可。

    public final class CallServerInterceptor implements Interceptor {
    
      @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        HttpCodec httpCodec = realChain.httpStream();
        StreamAllocation streamAllocation = realChain.streamAllocation();
        RealConnection connection = (RealConnection) realChain.connection();
        Request request = realChain.request();
    
        long sentRequestMillis = System.currentTimeMillis();
    
        realChain.eventListener().requestHeadersStart(realChain.call());
        httpCodec.writeRequestHeaders(request);
        realChain.eventListener().requestHeadersEnd(realChain.call(), request);
        
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
          if (responseBuilder == null) {
            // Write the request body if the "Expect: 100-continue" expectation was met.
            realChain.eventListener().requestBodyStart(realChain.call());
            long contentLength = request.body().contentLength();
            CountingSink requestBodyOut =
                new CountingSink(httpCodec.createRequestBody(request, contentLength));
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
            realChain.eventListener().requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
          } 
        }
        return response;
      }
    }

09.response響應監聽

  • responseHeadersStart和responseHeadersEnd代碼如下所示

    public final class CallServerInterceptor implements Interceptor {
    
      @Override public Response intercept(Chain chain) throws IOException {
    
        Response.Builder responseBuilder = null;
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
          if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
            httpCodec.flushRequest();
            realChain.eventListener().responseHeadersStart(realChain.call());
            responseBuilder = httpCodec.readResponseHeaders(true);
          }
        }
    
        httpCodec.finishRequest();
    
        if (responseBuilder == null) {
          realChain.eventListener().responseHeadersStart(realChain.call());
          responseBuilder = httpCodec.readResponseHeaders(false);
        }
    
        int code = response.code();
        if (code == 100) {
          // server sent a 100-continue even though we did not request one.
          // try again to read the actual response
          responseBuilder = httpCodec.readResponseHeaders(false);
    
          response = responseBuilder
                  .request(request)
                  .handshake(streamAllocation.connection().handshake())
                  .sentRequestAtMillis(sentRequestMillis)
                  .receivedResponseAtMillis(System.currentTimeMillis())
                  .build();
    
          code = response.code();
        }
    
        realChain.eventListener() .responseHeadersEnd(realChain.call(), response);
        return response;
      }
    }
  • responseBodyStart監聽

    • 響應體的讀取有些複雜,要根據不同類型的Content-Type決定如何讀取響應體,例如固定長度的、基於塊(chunk)數據的、未知長度的。具體看openResponseBody方法裡面的代碼。
    • 同時Http1與Http2也有不同的解析方式。下面以Http1為例。
    public final class Http1Codec implements HttpCodec {
    
      @Override public ResponseBody openResponseBody(Response response) throws IOException {
        streamAllocation.eventListener.responseBodyStart(streamAllocation.call);
        String contentType = response.header("Content-Type");
    
        if (!HttpHeaders.hasBody(response)) {
          Source source = newFixedLengthSource(0);
          return new RealResponseBody(contentType, 0, Okio.buffer(source));
        }
    
        if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
          Source source = newChunkedSource(response.request().url());
          return new RealResponseBody(contentType, -1L, Okio.buffer(source));
        }
    
        long contentLength = HttpHeaders.contentLength(response);
        if (contentLength != -1) {
          Source source = newFixedLengthSource(contentLength);
          return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
        }
    
        return new RealResponseBody(contentType, -1L, Okio.buffer(newUnknownLengthSource()));
      }
    }
  • responseBodyEnd監聽

    • 由下面代碼可知,當響應結束後,會調用連接callEnd回調(如果異常則會調用callFailed回調)
    public final class StreamAllocation {
      public void streamFinished(boolean noNewStreams, HttpCodec codec, long bytesRead, IOException e) {
        eventListener.responseBodyEnd(call, bytesRead);
        if (releasedConnection != null) {
          eventListener.connectionReleased(call, releasedConnection);
        }
        if (e != null) {
          eventListener.callFailed(call, e);
        } else if (callEnd) {
          eventListener.callEnd(call);
        }
      }
    }

10.如何監聽統計耗時

  • 如何消耗記錄時間

    • 在OkHttp庫中有一個EventListener類。該類是網絡事件的偵聽器。擴展這個類以監視應用程序的HTTP調用的數量、大小和持續時間。
    • 所有啟動/連接/獲取事件最終將接收到匹配的結束/釋放事件,要麼成功(非空參數),要麼失敗(非空可拋出)。
    • 比如,可以在開始鏈接記錄時間;dns開始,結束等方法解析記錄時間,可以計算dns的解析時間。
    • 比如,可以在開始請求記錄時間,記錄connectStart,connectEnd等方法時間,則可以計算出connect連接時間。
  • 代碼如下所示

    • Eventlistener只適用於沒有併發的情況,如果有多個請求併發執行我們需要使用Eventlistener. Factory來給每個請求創建一個Eventlistener。
    • 這個mRequestId是唯一值,可以選擇使用AtomicInteger自增+1的方式設置id,這個使用了cas保證多線程條件下的原子性特性。
    /**
     * <pre>
     *     @author yangchong
     *     email  : [email protected]
     *     time  : 2019/07/22
     *     desc  : EventListener子類
     *     revise:
     * </pre>
     */
    public class NetworkListener extends EventListener {
    
        private static final String TAG = "NetworkEventListener";
        private static AtomicInteger mNextRequestId = new AtomicInteger(0);
        private String mRequestId ;
    
        public static Factory get(){
            Factory factory = new Factory() {
                @NotNull
                @Override
                public EventListener create(@NotNull Call call) {
                    return new NetworkListener();
                }
            };
            return factory;
        }
    
        @Override
        public void callStart(@NotNull Call call) {
            super.callStart(call);
            //mRequestId = mNextRequestId.getAndIncrement() + "";
            //getAndAdd,在多線程下使用cas保證原子性
            mRequestId = String.valueOf(mNextRequestId.getAndIncrement());
            ToolLogUtils.i(TAG+"-------callStart---requestId-----"+mRequestId);
            saveEvent(NetworkTraceBean.CALL_START);
            saveUrl(call.request().url().toString());
        }
    
        @Override
        public void dnsStart(@NotNull Call call, @NotNull String domainName) {
            super.dnsStart(call, domainName);
            ToolLogUtils.d(TAG, "dnsStart");
            saveEvent(NetworkTraceBean.DNS_START);
        }
    
        @Override
        public void dnsEnd(@NotNull Call call, @NotNull String domainName, @NotNull List<InetAddress> inetAddressList) {
            super.dnsEnd(call, domainName, inetAddressList);
            ToolLogUtils.d(TAG, "dnsEnd");
            saveEvent(NetworkTraceBean.DNS_END);
        }
    
        @Override
        public void connectStart(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy) {
            super.connectStart(call, inetSocketAddress, proxy);
            ToolLogUtils.d(TAG, "connectStart");
            saveEvent(NetworkTraceBean.CONNECT_START);
        }
    
        @Override
        public void secureConnectStart(@NotNull Call call) {
            super.secureConnectStart(call);
            ToolLogUtils.d(TAG, "secureConnectStart");
            saveEvent(NetworkTraceBean.SECURE_CONNECT_START);
        }
    
        @Override
        public void secureConnectEnd(@NotNull Call call, @Nullable Handshake handshake) {
            super.secureConnectEnd(call, handshake);
            ToolLogUtils.d(TAG, "secureConnectEnd");
            saveEvent(NetworkTraceBean.SECURE_CONNECT_END);
        }
    
        @Override
        public void connectEnd(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress,
                               @NotNull Proxy proxy, @Nullable Protocol protocol) {
            super.connectEnd(call, inetSocketAddress, proxy, protocol);
            ToolLogUtils.d(TAG, "connectEnd");
            saveEvent(NetworkTraceBean.CONNECT_END);
        }
    
        @Override
        public void connectFailed(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy, @Nullable Protocol protocol, @NotNull IOException ioe) {
            super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
            ToolLogUtils.d(TAG, "connectFailed");
        }
    
        @Override
        public void requestHeadersStart(@NotNull Call call) {
            super.requestHeadersStart(call);
            ToolLogUtils.d(TAG, "requestHeadersStart");
            saveEvent(NetworkTraceBean.REQUEST_HEADERS_START);
        }
    
        @Override
        public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) {
            super.requestHeadersEnd(call, request);
            ToolLogUtils.d(TAG, "requestHeadersEnd");
            saveEvent(NetworkTraceBean.REQUEST_HEADERS_END);
        }
    
        @Override
        public void requestBodyStart(@NotNull Call call) {
            super.requestBodyStart(call);
            ToolLogUtils.d(TAG, "requestBodyStart");
            saveEvent(NetworkTraceBean.REQUEST_BODY_START);
        }
    
        @Override
        public void requestBodyEnd(@NotNull Call call, long byteCount) {
            super.requestBodyEnd(call, byteCount);
            ToolLogUtils.d(TAG, "requestBodyEnd");
            saveEvent(NetworkTraceBean.REQUEST_BODY_END);
        }
    
        @Override
        public void responseHeadersStart(@NotNull Call call) {
            super.responseHeadersStart(call);
            ToolLogUtils.d(TAG, "responseHeadersStart");
            saveEvent(NetworkTraceBean.RESPONSE_HEADERS_START);
        }
    
        @Override
        public void responseHeadersEnd(@NotNull Call call, @NotNull Response response) {
            super.responseHeadersEnd(call, response);
            ToolLogUtils.d(TAG, "responseHeadersEnd");
            saveEvent(NetworkTraceBean.RESPONSE_HEADERS_END);
        }
    
        @Override
        public void responseBodyStart(@NotNull Call call) {
            super.responseBodyStart(call);
            ToolLogUtils.d(TAG, "responseBodyStart");
            saveEvent(NetworkTraceBean.RESPONSE_BODY_START);
        }
    
        @Override
        public void responseBodyEnd(@NotNull Call call, long byteCount) {
            super.responseBodyEnd(call, byteCount);
            ToolLogUtils.d(TAG, "responseBodyEnd");
            saveEvent(NetworkTraceBean.RESPONSE_BODY_END);
        }
    
        @Override
        public void callEnd(@NotNull Call call) {
            super.callEnd(call);
            ToolLogUtils.d(TAG, "callEnd");
            saveEvent(NetworkTraceBean.CALL_END);
            generateTraceData();
            NetWorkUtils.timeoutChecker(mRequestId);
        }
    
        @Override
        public void callFailed(@NotNull Call call, @NotNull IOException ioe) {
            super.callFailed(call, ioe);
            ToolLogUtils.d(TAG, "callFailed");
        }
    
        private void generateTraceData(){
            NetworkTraceBean traceModel = IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);
            Map<String, Long> eventsTimeMap = traceModel.getNetworkEventsMap();
            Map<String, Long> traceList = traceModel.getTraceItemList();
            traceList.put(NetworkTraceBean.TRACE_NAME_TOTAL,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.CALL_START, NetworkTraceBean.CALL_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_DNS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.DNS_START, NetworkTraceBean.DNS_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_SECURE_CONNECT,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.SECURE_CONNECT_START, NetworkTraceBean.SECURE_CONNECT_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_CONNECT,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.CONNECT_START, NetworkTraceBean.CONNECT_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_HEADERS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.REQUEST_HEADERS_START, NetworkTraceBean.REQUEST_HEADERS_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_BODY,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.REQUEST_BODY_START, NetworkTraceBean.REQUEST_BODY_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_RESPONSE_HEADERS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.RESPONSE_HEADERS_START, NetworkTraceBean.RESPONSE_HEADERS_END));
            traceList.put(NetworkTraceBean.TRACE_NAME_RESPONSE_BODY,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.RESPONSE_BODY_START, NetworkTraceBean.RESPONSE_BODY_END));
        }
    
        private void saveEvent(String eventName){
            NetworkTraceBean networkTraceModel = IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);
            Map<String, Long> networkEventsMap = networkTraceModel.getNetworkEventsMap();
            networkEventsMap.put(eventName, SystemClock.elapsedRealtime());
        }
    
        private void saveUrl(String url){
            NetworkTraceBean networkTraceModel = IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);
            networkTraceModel.setUrl(url);
        }
    
    }
  • 關於執行順序,打印結果如下所示

    2020-09-22 20:50:15.351 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: dnsStart
    2020-09-22 20:50:15.373 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: dnsEnd
    2020-09-22 20:50:15.374 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: connectStart
    2020-09-22 20:50:15.404 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: secureConnectStart
    2020-09-22 20:50:15.490 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: secureConnectEnd
    2020-09-22 20:50:15.490 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: connectEnd
    2020-09-22 20:50:15.492 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: requestHeadersStart
    2020-09-22 20:50:15.492 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: requestHeadersEnd
    2020-09-22 20:50:15.528 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: responseHeadersStart
    2020-09-22 20:50:15.528 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: responseHeadersEnd
    2020-09-22 20:50:15.532 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: responseBodyStart
    2020-09-22 20:50:15.534 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: responseBodyEnd
    2020-09-22 20:50:15.547 28144-28277/cn.com.zwwl.bayuwen D/NetworkEventListener: callEnd

11.應用實踐之案例

image
image
image
image
image

  • 網絡攔截分析,主要是分析網絡流量損耗,以及request,respond過程時間。打造網絡分析工具……
  • 項目代碼地址:https://github.com/yangchong211/YCAndroidTool
  • 如果你覺得這個攔截網絡助手方便了測試,以及開發中查看網絡數據,可以star一下……

網絡攔截庫:https://github.com/yangchong211/YCAndroidTool

Leave a Reply

Your email address will not be published. Required fields are marked *