catalogue
- 01. Ask a question first
- 02.EventListener Callback principle
- 03. Request to end listening
- 04. DNS parsing starts End Listening
- 05. Connection starts end Listen
- 06.TLS connection starts and ends
- 07. Connect bind and release listener
- 08. Request Request listening
- Response Response monitoring
- 10. How do I monitor time statistics
- 11. Cases of applied practice
01. Ask a question first
- How does OkHttp count the time spent on each request?
- The OkHttp version provides an EventListener interface that lets the caller receive a series of events during a network request, such as DNS resolution, TSL/SSL connection, Response reception, and so on.
- By inheriting this interface, callers can monitor the number of network requests, the volume of traffic, and time (such as DNS resolution time, request time, response time, and so on) throughout the application.
02.EventListener Callback principle
- Let’s take a look at that
public abstract class EventListener { // Callback in the order requested public void callStart(Call call) {} // Domain name resolution public void dnsStart(Call call, String domainName) {} public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {} // Release the current Transmitter RealConnection public void connectionReleased(Call call, Connection connection) {} public void connectionAcquired(call, result){}; // Start the connection public void connectStart(call, route.socketAddress(), proxy){} / / request public void requestHeadersStart(@NotNull Call call){} public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) {} / / response public void requestBodyStart(@NotNull Call call) {} public void requestBodyEnd(@NotNull Call call, long byteCount) {} / / end public void callEnd(Call call) {} / / fail public void callFailed(Call call, IOException ioe) {}}Copy the code
03. Request to end listening
- CallStart (Call Call) The request starts
- This callback method is called when a Call (representing a request) is executed synchronously or added to an asynchronous queue.
- To explain this method is in the dispatcher executed/the enqueue before execution.
- Because of thread or event flow constraints, the request here is not really the request to execute. This method is also called only once if redirects and multiple domain retries occur.
final class RealCall implements Call { @Override public Response execute(a) 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(newAsyncCall(responseCallback)); }}Copy the code
- CallFailed /callEnd Request exception and request end
- Each callStart corresponds to a callFailed or callEnd.
- CallFailed is invoked in two cases, the first when an exception occurs during the execution of the request. The second is when an exception occurs when the input stream is closed after the request ends.
final class RealCall implements Call { @Override public Response execute(a) 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); throwe; }}final class AsyncCall extends NamedRunnable { @Override protected void execute(a) { try { Response response = getResponseWithInterceptorChain(); } catch (IOException e) { eventListener.callFailed(RealCall.this, e); }}}}/ / the second 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); }... }}Copy the code
- CallEnd also has two invocation scenarios. The first is also when closing the stream. The second is when the connection is released.
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(a) {...if(releasedConnection ! =null) { eventListener.connectionReleased(call, releasedConnection); eventListener.callEnd(call); }}}Copy the code
- Why is a closed flow separated from a closed connection?
- In the HTTP2 version, multiple streams are allowed to open on a connection, and OkHttp uses StreamAllocation as a bridge between streams and connections. When a stream is closed, check to see if there are any other streams on the connection. If there are none, close the connection.
- StreamFinished and Release work the same way, closing the current stream and checking to see if the connection needs to be closed. The difference is that when the caller manually cancels the request, the release method is called and it is the caller’s responsibility to close the request output stream and response input stream.
04. DNS parsing starts End Listening
- DnsStart began
- The lookup(String Hostname) method represents the domain name resolution process, dnsStart/dnsEnd is called before and after the lookup
- DNS resolution is the process of asking the Domain Name System (DNS) server to resolve a Domain Name into an IP address. Domain name resolution is done by the InetAddress class in the JDK.
/** 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); }}Copy the code
- So where is the RouteSelector class called
public final class StreamAllocation { public StreamAllocation(ConnectionPool connectionPool, Address address, Call call, EventListener eventListener, Object callStackTrace) { this.routeSelector = newRouteSelector(address, routeDatabase(), call, eventListener); }}Copy the code
05. Connection starts end Listen
- ConnectStart The connection starts
- OkHttp uses the Socket interface to establish a Tcp connection, so the connection refers to the process by which a Socket establishes a connection.
- When a connection is reused, connectStart/connectEnd is not called. ConnectStart /connectEnd will be called multiple times after the request is redirected to the new domain name.
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); } Copy the code
- ConnectEnd The connection ends
- Because there are two types of connections created (server direct connection and tunnel proxy), callEnd has two places to call. To use SSL on a proxy-based connection, you need to send a separate CONECT request.
- During a connection, the connectEnd is called back whether the Socket connection fails or the TSL/SSL handshake fails.
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); }}Copy the code
06.TLS connection starts and ends
- Start the connection, as shown below
- As you saw above, an establishProtocol method is performed after the Socket establishes a connection, which serves as the TSL/SSL handshake.
- When the presence of redirection or connection retry, secureConnectStart/secureConnectEnd is called many times.
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); } Copy the code
- You can see this in combination with connection listening
- If the HTTPS connection is used, the TLS communication is required after the TCP connection is successful. The connection process is complete only after the TLS communication is complete. That is, connectEnd is invoked after secureConnectEnd.
- So this is the order
- connectStart —> secureConnectStart —> secureConnectEnd —> ConnectEnd
07. Connect bind and release listener
- Because OkHttp is based on connection reuse, the current connection is not closed immediately after a request is completed, but is placed into the connection pool.
- When there is a request for the same domain name, the system removes the corresponding connection from the connection pool, reducing the frequent creation and destruction of connections.
- When a connection is fetched from the connection pool based on a request and the input/output stream is opened, the input/output stream is acquired and the release stream is released.
- If the direct reuse of StreamAllocation connection, do not call 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) { // Check the cache a second time 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) {// If the cache does not exist, create a new connection 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; } Copy the code
- ConnectionAcquired is called after a successful connection.
- However, there is no connection step in the case of connection reuse and connectAcquired will be called after obtaining a cached connection. Since StreamAllocation is a bridge between “Stream” and “Connection”, a RealConnection reference is held in StreamAllocation. StreamAllocation in finding available connection order: StreamAllocation. RealConnection – > ConnectionPool – > ConnectionPool – > new RealConnection
08. Request Request listening
- In OkHttp, HttpCodec is responsible for encoding and decoding requests and responses according to the Http protocol, including sending the request header, sending the request body, reading the response header, and reading the response body.
- RequestHeaders start and end with the CallServerInterceptor interceptor code.
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 = newCountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener().requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); }}returnresponse; }}Copy the code
Response Response monitoring
- The responseHeadersStart and responseHeadersEnd codes are shown below
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); returnresponse; }}Copy the code
- ResponseBodyStart listening
- The reading of the response body is somewhat complicated. It depends on different content-Types, such as those with fixed length, those based on chunk data, and those of unknown length. Look at the code inside the openResponseBody method.
- Also, Http1 and Http2 have different parsing methods. The following uses Http1 as an example.
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())); }}Copy the code
- ResponseBodyEnd listening
- As you can see from the following code, when the response ends, the connection callEnd callback is called (or the callFailed callback is called if there is an exception)
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); }}}Copy the code
10. How do I monitor time statistics
- How to consume recording time
- There is an EventListener class in the OkHttp library. This class is a listener for network events. Extend this class to monitor the number, size, and duration of HTTP calls to your application.
- All start/connect/get events will eventually receive a matching end/release event that either succeeds (non-empty arguments) or fails (non-empty throwable).
- For example, you can log the time at the start of the link; DNS start, end and other methods to resolve the record time, you can calculate the DNS resolution time.
- For example, you can calculate the connect connection time by recording the time of the start request, connectStart, connectEnd, etc.
- The code is shown below
- Eventlistener only works if there is no concurrency. If multiple requests are executed concurrently, we need to create an Eventlistener for each request using eventListener.factory.
- The mRequestId is unique and the ID can optionally be set with AtomicInteger incrementing by +1, which uses CAS to ensure atomicity in multithreading conditions.
/** * <pre> * @authorYangchong * Email: [email protected] * time: 2019/07/22 * desc: EventListener subclass * 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(a){ Factory factory = new Factory() { @NotNull @Override public EventListener create(@NotNull Call call) { return newNetworkListener(); }};return factory; } @Override public void callStart(@NotNull Call call) { super.callStart(call); //mRequestId = mNextRequestId.getAndIncrement() + ""; //getAndAdd, use CAS to ensure atomicity in multithreading 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(a){ 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.SE CURE_CONNECT_START, NetworkTraceBean.SECURE_CONNECT_END)); traceList.put(NetworkTraceBean.TRACE_NAME_CONNECT,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.CONNECT_S TART, NetworkTraceBean.CONNECT_END)); traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_HEADERS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.R EQUEST_HEADERS_START, NetworkTraceBean.REQUEST_HEADERS_END)); traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_BODY,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.REQU EST_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.RES PONSE_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); }}Copy the code
- For the order of execution, the print is as follows
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 Copy the code
11. Cases of applied practice
- Network intercept analysis analyzes network traffic loss and request and respond process time. Build web analytics tools…
- Project code address: github.com/yangchong21…
- If you find the Blocking Network assistant convenient for testing and viewing network data during development, you can star…