• 网络库OKHTTP(2)面试题


    序、慢慢来才是最快的方法。

    背景

    OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作。

    OKHttp源码官网 

    问1:OKHttp有哪些拦截器,分别起什么作用

    必考题,头两年没考,今年必考。

    OKHTTP的拦截器是把所有的拦截器放到一个list里,然后每次依次执行拦截器,并且在每个拦截器分成三部分:

    • 预处理拦截器内容
    • 通过proceed方法把请求交给下一个拦截器
    • 下一个拦截器处理完成并返回,后续处理工作。

    这样依次下去就形成了一个链式调用,看看源码,具体有哪些拦截器:

    getResponseWithInterceptorChain()

    1. @Throws(IOException::class)
    2. internal fun getResponseWithInterceptorChain(): Response {
    3. // Build a full stack of interceptors.
    4. val interceptors = mutableListOf()
    5. interceptors += client.interceptors
    6. interceptors += RetryAndFollowUpInterceptor(client)
    7. interceptors += BridgeInterceptor(client.cookieJar)
    8. interceptors += CacheInterceptor(client.cache)
    9. interceptors += ConnectInterceptor
    10. if (!forWebSocket) {
    11. interceptors += client.networkInterceptors
    12. }
    13. interceptors += CallServerInterceptor(forWebSocket)
    14. val chain = RealInterceptorChain(
    15. call = this,
    16. interceptors = interceptors,
    17. index = 0,
    18. exchange = null,
    19. request = originalRequest,
    20. connectTimeoutMillis = client.connectTimeoutMillis,
    21. readTimeoutMillis = client.readTimeoutMillis,
    22. writeTimeoutMillis = client.writeTimeoutMillis
    23. )
    24. var calledNoMoreExchanges = false
    25. try {
    26. val response = chain.proceed(originalRequest)
    27. if (isCanceled()) {
    28. response.closeQuietly()
    29. throw IOException("Canceled")
    30. }
    31. return response
    32. } catch (e: IOException) {
    33. calledNoMoreExchanges = true
    34. throw noMoreExchanges(e) as Throwable
    35. } finally {
    36. if (!calledNoMoreExchanges) {
    37. noMoreExchanges(null)
    38. }
    39. }
    40. }

    根据源码可知,一共七个拦截器:

    • addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
    • RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的充实工作,重定向的后续请求工作。跟他的名字一样,就是做重试工作还有一些连接跟踪工作。
    • BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
    • CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
    • ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec
    • networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,所以用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
    • CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。

    问2:OKHttp请求的流程?

    我们知道Okhttp中通过okhttpClient对象是通过Builder对象初始化出来的,此处Builder的用法是建造者模式,建造者模式主要是分离出外部类的属性初始化,而初始化属性交给了内部类Buidler类,这么做的好处是外部类不用关心属性的初始化。 而在初始化的时候有interceptorsnetworkInterceptors两种拦截器的初始化,还有dispatcher(分发器)的初始化,以及后面需要讲到的cache(缓存)初始化等。

    初始化完了后通过builder的build方法构造出okhttpClient对象,该类被称作客户端类,通过它的newCall方法返回RealCall对象,在newCall过程的过程中需要request的信息,request信息包装了url、method、headers、body等信息。最后通过RealCall的同步或异步方法交给了okhttpClientdispatcher来处理,在处理同步或异步之前都会判断有没有正在executed,所以我们不能对同一个RealCall调用异步或同步方法。

    在异步的时候会把RealCall给包装成一个AsyncCall,它是一个runnable对象。接着就来到了分发器异步处理部分,首先会把AsyncCall加入到readyAsyncCalls的集合中,该集合表示准备阶段的请求集合,紧接着从runningAsyncCalls(该集合装的都是要即将请求的集合)readyAsyncCalls集合中找相同host的AsyncCall,如果找到了会把当中记录的相同host的个数给该AsyncCall注意这里保存host个数用的原子性的AtomicInteger来记录的

    接着会去判断最大的请求是否大于64以及相同host是否大于5个,这里也是okhttp面试高频知识点,如果都通过的话,会把当前的AsyncCall的相同host记录数加一,接着会加入到runningAsyncCalls集合中,接着循环遍历刚符合条件的AsyncCall,通过线程池去执行AsyncCall,注意此处的线程池的配置是没有核心线程,总的线程个数是没有限制的,也就是说都是非核心线程,并且个数没有限制,非核心线程等待的时间是60秒,并且使用的任务队列是SynchronousQueue,它是一个没有容量的阻塞队列,只会当里面没有任务的时候,才能往里面放任务,当放完之后,只能等它的任务被取走才能放,这不就是jdk里面提供的Executors.newCachedThreadPool线程池吗,可能是okhttp想自己定义线程工厂的参数吧,定义线程的名字。

    所以到这里才会进入到子线程,由于AsyncCall是一个runnable,因此最终执行来到了它的run方法吧,run方法最终会走到execute方法,该方法来到了okhttp最有意思的单链表结构的拦截器部分,它会把所有的拦截器组装成一个集合,然后传给RealInterceptorChainprocess方法,在该方法中,会先把下一个RealInterceptorChain初始化出来,然后把下一个RealInterceptorChain传给当前Interceptor的intercept方法,最终一个个的response返回到AsyncCallexecute方法。

    处理完当前的AsyncCall后,会交给dispatcher,它会将该AsyncCall的host数减一,并且把它从runningAsyncCalls集合中移除,接着再从readyAsyncCalls集合中拿剩下的AsyncCall继续执行,直到执行完readyAsyncCalls里面的AsyncCall

    问3:OkHttp怎么实现连接池?

    总结

    连接池部分主要是在RealConnectionPool类中,该类用connections(双端队列)存储所有的连接,cleanupRunnable是专门用来清除超时的RealConnection,既然有清除的任务,那肯定有清除的线程池,没错,该线程池(executor)跟okhttp处理异步时候的线程池是一样的,keepAliveDurationNs表示每一个连接keep-alive的时间,默认是5分钟,maxIdleConnections连接池的最大容量,默认是5个。RealConnection中有transmitters字段,用来保存该连接的transmitter个数,通过里面的transmitter个数来标记该RealConnection有没有在使用中。

    连接池的意义?

    • 频繁的进行建立Sokcet连接(TCP三次握手)和断开Socket(TCP四次分手)是非常消耗网络资源和浪费时间的,HTTP中的keepalive连接对于 降低延迟和提升速度有非常重要的作用。
    • 复用连接就需要对连接进行管理,这里就引入了连接池的概念。
    • Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间),连接池有ConectionPool实现,对连接进行回收和管理。

    为什么需要连接池?

    频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的,所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。

    keepalive机制是什么呢?

    也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。所以连接的多次使用,也就是复用就变得格外重要了,而复用连接就需要对连接进行管理,于是就有了连接池的概念。

    OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。

    怎么实现的?

            1,首先,ConectionPool中维护了一个双端队列Deque,也就是两端都可以进出的队列,用来存储连接。

            2.然后在ConnectInterceptor,也就是负责建立连接的拦截器中,首先会找可用连接,也就是从连接池中去获取连接,具体的就是会调用到ConectionPool的get方法。

    1. RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    2. assert (Thread.holdsLock(this));
    3. for (RealConnection connection : connections) {
    4. if (connection.isEligible(address, route)) {
    5. streamAllocation.acquire(connection, true);
    6. return connection;
    7. }
    8. }
    9. return null;
    10. }

    也就是遍历了双端队列,如果连接有效,就会调用acquire方法计数并返回这个连接。

            3.如果没找到可用连接,就会创建新连接,并会把这个建立的连接加入到双端队列中,同时开始运行线程池中的线程,其实就是调用了ConectionPool的put方法。

    1. public final class ConnectionPool {
    2. void put(RealConnection connection) {
    3. if (!cleanupRunning) {
    4. //没有连接的时候调用
    5. cleanupRunning = true;
    6. executor.execute(cleanupRunnable);
    7. }
    8. connections.add(connection);
    9. }
    10. }

            4.其实这个线程池中只有一个线程,是用来清理连接的,也就是上述的cleanupRunnable

    1. private final Runnable cleanupRunnable = new Runnable() {
    2. @Override
    3. public void run() {
    4. while (true) {
    5. //执行清理,并返回下次需要清理的时间。
    6. long waitNanos = cleanup(System.nanoTime());
    7. if (waitNanos == -1) return;
    8. if (waitNanos > 0) {
    9. long waitMillis = waitNanos / 1000000L;
    10. waitNanos -= (waitMillis * 1000000L);
    11. synchronized (ConnectionPool.this) {
    12. //在timeout时间内释放锁
    13. try {
    14. ConnectionPool.this.wait(waitMillis, (int) waitNanos);
    15. } catch (InterruptedException ignored) {
    16. }
    17. }
    18. }
    19. }
    20. }
    21. };

    这个runnable会不停的调用cleanup方法清理线程池,并返回下一次清理的时间间隔,然后进入wait等待。

    怎么清理的呢?看看源码:

    1. long cleanup(long now) {
    2. synchronized (this) {
    3. //遍历连接
    4. for (Iterator i = connections.iterator(); i.hasNext(); ) {
    5. RealConnection connection = i.next();
    6. //检查连接是否是空闲状态,
    7. //不是,则inUseConnectionCount + 1
    8. //是 ,则idleConnectionCount + 1
    9. if (pruneAndGetAllocationCount(connection, now) > 0) {
    10. inUseConnectionCount++;
    11. continue;
    12. }
    13. idleConnectionCount++;
    14. // If the connection is ready to be evicted, we're done.
    15. long idleDurationNs = now - connection.idleAtNanos;
    16. if (idleDurationNs > longestIdleDurationNs) {
    17. longestIdleDurationNs = idleDurationNs;
    18. longestIdleConnection = connection;
    19. }
    20. }
    21. //如果超过keepAliveDurationNs或maxIdleConnections,
    22. //从双端队列connections中移除
    23. if (longestIdleDurationNs >= this.keepAliveDurationNs
    24. || idleConnectionCount > this.maxIdleConnections) {
    25. connections.remove(longestIdleConnection);
    26. } else if (idleConnectionCount > 0) { //如果空闲连接次数>0,返回将要到期的时间
    27. // A connection will be ready to evict soon.
    28. return keepAliveDurationNs - longestIdleDurationNs;
    29. } else if (inUseConnectionCount > 0) {
    30. // 连接依然在使用中,返回保持连接的周期5分钟
    31. return keepAliveDurationNs;
    32. } else {
    33. // No connections, idle or in use.
    34. cleanupRunning = false;
    35. return -1;
    36. }
    37. }
    38. closeQuietly(longestIdleConnection.socket());
    39. // Cleanup again immediately.
    40. return 0;
    41. }

    也就是当如果空闲连接maxIdleConnections超过5个或者keepalive时间大于5分钟,则将该连接清理掉。

    这里有个问题,怎样属于空闲连接?

    1. public void acquire(RealConnection connection, boolean reportedAcquired) {
    2. assert (Thread.holdsLock(connectionPool));
    3. if (this.connection != null) throw new IllegalStateException();
    4. this.connection = connection;
    5. this.reportedAcquired = reportedAcquired;
    6. connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
    7. }

    RealConnection中,有一个StreamAllocation虚引用列表allocations。每创建一个连接,就会把连接对应的StreamAllocationReference添加进该列表中,如果连接关闭以后就将该对象移除。

            5.连接池的工作就这么多,并不复杂,主要就是管理双端队列Deque,可以用的连接就直接用,然后定期清理连接,同时通过对StreamAllocation的引用计数实现自动回收。

    最后

    • 连接池是为了解决频繁的进行建立Sokcet连接(TCP三次握手)和断开Socket(TCP四次分手)。
    • Okhttp的连接池支持最大5个链路的keep-alive连接,并且默认keep-alive的时间是5分钟。
    • 连接池实现的类是RealConnectionPool,它负责存储与清除的工作,存储是通过ArrayDeque的双端队列存储,删除交给了线程池处理cleanupRunnable的任务。
    • 在每次创建RealConnection或从连接池中拿一次RealConnection会给RealConnection的 transmitters集合添加一个若引用的transmitter对象,添加它主要是为了后面判断该连接是否在使用中
    • 在连接池中找连接的时候会对比连接池中相同host的连接。
    • 如果在连接池中找不到连接的话,会创建连接,创建完后会存储到连接池中。
    • 在把连接放入连接池中时,会把清除操作的任务放入到线程池中执行,删除任务中会判断当前连接有没有在使用中,有没有正在使用通过RealConnection的transmitters集合的size是否为0来判断,如果不在使用中,找出空闲时间最长的连接,如果空闲时间最长的连接超过了keep-alive默认的5分钟或者空闲的连接数超过了最大的keep-alive连接数5个的话,会把存活时间最长的连接从连接池中删除。保证keep-alive的最大空闲时间和最大的连接数。


     

    问4:OkHttp里面用到了什么设计模式

    责任链模式

    可以说是okhttp的精髓所在了,主要体现就是拦截器的使用,具体代码可以看看上述的拦截器介绍。

    建造者模式

    在Okhttp中,建造者模式也是用的挺多的,主要用处是将对象的创建与表示相分离,用Builder组装各项配置。

    工厂模式

    工厂模式和建造者模式类似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。

    例子有CacheInterceptor拦截器中又个CacheStrategy对象:

    1. CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    2. public Factory(long nowMillis, Request request, Response cacheResponse) {
    3. this.nowMillis = nowMillis;
    4. this.request = request;
    5. this.cacheResponse = cacheResponse;
    6. if (cacheResponse != null) {
    7. this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
    8. this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
    9. Headers headers = cacheResponse.headers();
    10. for (int i = 0, size = headers.size(); i < size; i++) {
    11. String fieldName = headers.name(i);
    12. String value = headers.value(i);
    13. if ("Date".equalsIgnoreCase(fieldName)) {
    14. servedDate = HttpDate.parse(value);
    15. servedDateString = value;
    16. } else if ("Expires".equalsIgnoreCase(fieldName)) {
    17. expires = HttpDate.parse(value);
    18. } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
    19. lastModified = HttpDate.parse(value);
    20. lastModifiedString = value;
    21. } else if ("ETag".equalsIgnoreCase(fieldName)) {
    22. etag = value;
    23. } else if ("Age".equalsIgnoreCase(fieldName)) {
    24. ageSeconds = HttpHeaders.parseSeconds(value, -1);
    25. }
    26. }
    27. }
    28. }

    观察者模式 

    关于Okhttp中websocket的使用,由于webSocket属于长连接,所以需要进行监听,这里是用到了观察者模式:

    1. final WebSocketListener listener;
    2. @Override public void onReadMessage(String text) throws IOException {
    3. listener.onMessage(this, text);
    4. }

    单例模式

    每个项目都会有。

    问5:论如何优雅地知道OkHttp的请求时间

    OkHttp如何进行各个请求环节的耗时统计呢?

    OkHttp 版本提供了EventListener接口,可以让调用者接收一系列网络请求过程中的事件,例如DNS解析、TSL/SSL连接、Response接收等。通过继承此接口,调用者可以监视整个应用中网络请求次数、流量大小、耗时(比如dns解析时间,请求时间,响应时间等等)情况。

    1. public abstract class EventListener {
    2. // 按照请求顺序回调
    3. public void callStart(Call call) {}
    4. // 域名解析
    5. public void dnsStart(Call call, String domainName) {}
    6. public void dnsEnd(Call call, String domainName, List inetAddressList) {}
    7. // 释放当前Transmitter的RealConnection
    8. public void connectionReleased(Call call, Connection connection) {}
    9. public void connectionAcquired(call, result){};
    10. // 开始连接
    11. public void connectStart(call, route.socketAddress(), proxy){}
    12. // 请求
    13. public void requestHeadersStart(@NotNull Call call){}
    14. public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) {}
    15. // 响应
    16. public void requestBodyStart(@NotNull Call call) {}
    17. public void requestBodyEnd(@NotNull Call call, long byteCount) {}
    18. // 结束
    19. public void callEnd(Call call) {}
    20. // 失败
    21. public void callFailed(Call call, IOException ioe) {}
    22. }

    请求开始结束监听

    1. inal class RealCall implements Call {
    2. @Override
    3. public Response execute() throws IOException {
    4. eventListener.callStart(this);
    5. client.dispatcher().executed(this);
    6. Response result = getResponseWithInterceptorChain();
    7. if (result == null) throw new IOException("Canceled");
    8. return result;
    9. }
    10. @Override
    11. public void enqueue(Callback responseCallback) {
    12. eventListener.callStart(this);
    13. client.dispatcher().enqueue(new AsyncCall(responseCallback));
    14. }
    15. }

    如何消耗记录时间

    OkHttp库中有一个EventListener类。该类是网络事件的侦听器。扩展这个类以监视应用程序的HTTP调用的数量、大小和持续时间。所有启动/连接/获取事件最终将接收到匹配的结束/释放事件,要么成功(非空参数),要么失败(非空可抛出)。

    比如,可以在开始链接记录时间;dns开始,结束等方法解析记录时间,可以计算dns的解析时间。

    比如,可以在开始请求记录时间,记录connectStart,connectEnd等方法时间,则可以计算出connect连接时间。

    代码如下所示:

    Eventlistener只适用于没有并发的情况,如果有多个请求并发执行我们需要使用Eventlistener. Factory来给每个请求创建一个Eventlistener。这个mRequestId是唯一值,可以选择使用AtomicInteger自增+1的方式设置id,这个使用了cas保证多线程条件下的原子性特性。

    1. /**
    2. *
    3. * @author yangchong
    4. * email : yangchong211@163.com
    5. * time : 2019/07/22
    6. * desc : EventListener子类
    7. * revise:
    8. *
  • */
  • 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 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);
  • MapLong> eventsTimeMap = traceModel.getNetworkEventsMap();
  • MapLong> 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);
  • MapLong> networkEventsMap = networkTraceModel.getNetworkEventsMap();
  • networkEventsMap.put(eventName, SystemClock.elapsedRealtime());
  • }
  • private void saveUrl(String url){
  • NetworkTraceBean networkTraceModel = IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);
  • networkTraceModel.setUrl(url);
  • }
  • }
  • 参考

    网络库OKHttp(1)流程+拦截器-CSDN博客

    谈谈OKHttp的几道面试题 - 简书

    面试官:Okhttp连接池是咋实现的? - 掘金

    论如何优雅地知道OkHttp的请求时间

  • 相关阅读:
    Gitlab API调用给每个人生成一个token,操作api
    如何看待AIGC技术?【模板】
    sqlmap结合dnslog外带注入
    js浮点数精度问题详解
    【513. 找树左下角的值】
    微服务中的熔断、降级和限流
    盲注原理基础
    智慧城市的发展趋势
    Easy EDA #学习笔记09# | ESP32-WROOM-32E模组ESP32-DevKitC-V4开发板 一键下载电路
    scrollIntoView多重校验rules滚动到指定位置
  • 原文地址:https://blog.csdn.net/qq_37492806/article/details/133881895