OKHttp源码解析(二)-责任链

在典型的责任链设计模式里,很多对象由每一个对象对其下级的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。责任链在现实生活中的一种场景就是面试,当某轮面试官觉得你没有资格进入下一轮的时候可以否定你,不然会让下一轮的面试官继续面试。

一、责任链的执行过程

在 OkHttp 里面,责任链的执行模式与之稍有不同。这里我们主要来分析一下在 OkHttp 里面,责任链是如何执行的,至于每个链条里面的具体逻辑,我们会在随后一一说明。

有两个地方需要我们注意:

是当创建一个责任链 RealInterceptorChain 的时候,我们传入的第 5 个参数是 0。该参数名为 index,会被赋值给 RealInterceptorChain 实例内部的同名全局变量。
当启用责任链的时候,会调用它的 proceed(Request) 方法。

下面是 proceed(Request) 方法的定义:

    @Override public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
    }

这里又调用了内部的重载的 proceed() 方法。下面我们对该方法进行了简化:

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
        RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
        // ...
        // 调用责任链的下一个拦截器
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
            connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
            writeTimeout);
        Interceptor interceptor = interceptors.get(index);
        Response response = interceptor.intercept(next);
        // ...
        return response;
    }

注意到这里使用责任链进行处理的时候,会新建下一个责任链并把 index+1 作为下一个责任链的 index。然后,我们使用 index 从拦截器列表中取出一个拦截器,调用它的 intercept() 方法,并把下一个执行链作为参数传递进去。

这样,当下一个拦截器希望自己的下一级继续处理这个请求的时候,可以调用传入的责任链的 proceed() 方法;如果自己处理完毕之后,下一级不需要继续处理,那么就直接返回一个 Response 实例即可。因为,每次都是在当前的 index 基础上面加 1,所以能在调用 proceed() 的时候准确地从拦截器列表中取出下一个拦截器进行处理。

我们还要注意的地方是之前提到过重试拦截器,这种拦截器会在内部启动一个 while 循环,并在循环体中调用执行链的 proceed() 方法来实现请求的不断重试。这是因为在它那里的拦截器链的 index 是固定的,所以能够每次调用 proceed() 的时候,都能够从自己的下一级执行一遍链条。下面就是这个责任链的执行过程:

在这里插入图片描述

二、RetryAndFollowUpInterceptor(重试和重定向)

RetryAndFollowUpInterceptor 主要用来当请求失败的时候进行重试,以及在需要的情况下进行重定向。我们上面说,责任链会在进行处理的时候调用第一个拦截器的 intercept() 方法。如果我们在创建 OkHttp 客户端的时候没有加入自定义拦截器,那么
RetryAndFollowUpInterceptor 就是我们的责任链中最先被调用的拦截器。

    @Override public Response intercept(Chain chain) throws IOException {
        // ...
        // 注意这里我们初始化了一个 StreamAllocation 并赋值给全局变量,它的作用我们后面会提到
        StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
        // 用来记录重定向的次数
        int followUpCount = 0;
        Response priorResponse = null;
        while (true) {
            if (canceled) {
                streamAllocation.release();
                throw new IOException("Canceled");
            }

            Response response;
            boolean releaseConnection = true;
            try {
                // 这里从当前的责任链开始执行一遍责任链,是一种重试的逻辑
                response = realChain.proceed(request, streamAllocation, null, null);
                releaseConnection = false;
            } catch (RouteException e) {
                // 调用 recover 方法从失败中进行恢复,如果可以恢复就返回true,否则返回false
                if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
                    throw e.getLastConnectException();
                }
                releaseConnection = false;
                continue;
            } catch (IOException e) {
                // 重试与服务器进行连接
                boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
                if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
                releaseConnection = false;
                continue;
            } finally {
                // 如果 releaseConnection 为 true 则表明中间出现了异常,需要释放资源
                if (releaseConnection) {
                    streamAllocation.streamFailed(null);
                    streamAllocation.release();
                }
            }

            // 使用之前的响应 priorResponse 构建一个响应,这种响应的响应体 body 为空
            if (priorResponse != null) {
                response = response.newBuilder()
                        .priorResponse(priorResponse.newBuilder().body(null).build())
                        .build();
            }

            // 根据得到的响应进行处理,可能会增加一些认证信息、重定向或者处理超时请求
            // 如果该请求无法继续被处理或者出现的错误不需要继续处理,将会返回 null
            Request followUp = followUpRequest(response, streamAllocation.route());

            // 无法重定向,直接返回之前的响应
            if (followUp == null) {
                if (!forWebSocket) {
                    streamAllocation.release();
                }
                return response;
            }

            // 关闭资源
            closeQuietly(response.body());

            // 达到了重定向的最大次数,就抛出一个异常
            if (++followUpCount > MAX_FOLLOW_UPS) {
                streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
            }

            if (followUp.body() instanceof UnrepeatableRequestBody) {
                streamAllocation.release();
                throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
            }

            // 这里判断新的请求是否能够复用之前的连接,如果无法复用,则创建一个新的连接
            if (!sameConnection(response, followUp.url())) {
                streamAllocation.release();
                streamAllocation = new StreamAllocation(client.connectionPool(),
                        createAddress(followUp.url()), call, eventListener, callStackTrace);
                this.streamAllocation = streamAllocation;
            } else if (streamAllocation.codec() != null) {
                throw new IllegalStateException("Closing the body of " + response
                        + " didn't close its backing stream. Bad interceptor?");
            }

            request = followUp;
            priorResponse = response;
        }
    }

以上的代码主要用来根据错误的信息做一些处理,会根据服务器返回的信息判断这个请求是否可以重定向,或者是否有必要进行重试。如果值得去重试就会新建或者复用之前的连接在下一次循环中进行请求重试,否则就将得到的请求包装之后返回给用户。这里,我们提到了 StreamAllocation 对象,它相当于一个管理类,维护了服务器连接、并发流和请求之间的关系,该类还会初始化一个 Socket 连接对象,获取输入/输出流对象。同时,还要注意这里我们通过 client.connectionPool() 传入了一个连接池对象 ConnectionPool。这里我们只是初始化了这些类,但实际在当前的方法中并没有真正用到这些类,而是把它们传递到下面的拦截器里来从服务器中获取请求的响应。稍后,我们会说明这些类的用途,以及之间的关系。

三、BridgeInterceptor(桥接)

桥拦截器 BridgeInterceptor 用于从用户的请求中构建网络请求,然后使用该请求访问网络,最后从网络响应当中构建用户响应。相对来说这个拦截器的逻辑比较简单,只是用来对请求进行包装,并将服务器响应转换成用户友好的响应:

    public final class BridgeInterceptor implements Interceptor {
        @Override public Response intercept(Chain chain) throws IOException {
            Request userRequest = chain.request();
            // 从用户请求中获取网络请求构建者
            Request.Builder requestBuilder = userRequest.newBuilder();
            // ...
            // 执行网络请求
            Response networkResponse = chain.proceed(requestBuilder.build());
            // ...
            // 从网络响应中获取用户响应构建者
            Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
            // ...
            // 返回用户响应
            return responseBuilder.build();
        }
    }

四、CacheInterceptor(缓存)

缓存拦截器会根据请求的信息和缓存的响应的信息来判断是否存在缓存可用,如果有可以使用的缓存,那么就返回该缓存该用户,否则就继续责任链来从服务器中获取响应。当获取到响应的时候,又会把响应缓存到磁盘上面。以下是这部分的逻辑:

    public final class CacheInterceptor implements Interceptor {
        @Override public Response intercept(Chain chain) throws IOException {
            Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
            long now = System.currentTimeMillis();
            // 根据请求和缓存的响应中的信息来判断是否存在缓存可用
            CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
            Request networkRequest = strategy.networkRequest; // 如果该请求没有使用网络就为空
            Response cacheResponse = strategy.cacheResponse; // 如果该请求没有使用缓存就为空
            if (cache != null) {
                cache.trackResponse(strategy);
            }
            if (cacheCandidate != null && cacheResponse == null) {
                closeQuietly(cacheCandidate.body());
            }
            // 请求不使用网络并且不使用缓存,相当于在这里就拦截了,没必要交给下一级(网络请求拦截器)来执行
            if (networkRequest == null && cacheResponse == null) {
                return new Response.Builder()
                        .request(chain.request())
                        .protocol(Protocol.HTTP_1_1)
                        .code(504)
                        .message("Unsatisfiable Request (only-if-cached)")
                        .body(Util.EMPTY_RESPONSE)
                        .sentRequestAtMillis(-1L)
                        .receivedResponseAtMillis(System.currentTimeMillis())
                        .build();
            }
            // 该请求使用缓存,但是不使用网络:从缓存中拿结果,没必要交给下一级(网络请求拦截器)执行
            if (networkRequest == null) {
                return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();
            }
            Response networkResponse = null;
            try {
                // 这里调用了执行链的处理方法,实际就是交给自己的下一级来执行了
                networkResponse = chain.proceed(networkRequest);
            } finally {
                if (networkResponse == null && cacheCandidate != null) {
                    closeQuietly(cacheCandidate.body());
                }
            }
            // 这里当拿到了网络请求之后调用,下一级执行完毕会交给它继续执行,如果使用了缓存就把请求结果更新到缓存里
            if (cacheResponse != null) {
                // 服务器返回的结果是304,返回缓存中的结果
                if (networkResponse.code() == HTTP_NOT_MODIFIED) {
                    Response response = cacheResponse.newBuilder()
                            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                            .cacheResponse(stripBody(cacheResponse))
                            .networkResponse(stripBody(networkResponse))
                            .build();
                    networkResponse.body().close();
                    cache.trackConditionalCacheHit();
                    // 更新缓存
                    cache.update(cacheResponse, response);
                    return response;
                } else {
                    closeQuietly(cacheResponse.body());
                }
            }
            Response response = networkResponse.newBuilder()
                    .cacheResponse(stripBody(cacheResponse))
                    .networkResponse(stripBody(networkResponse))
                    .build();
            // 把请求的结果放进缓存里
            if (cache != null) {
                if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                    CacheRequest cacheRequest = cache.put(response);
                    return cacheWritingResponse(cacheRequest, response);
                }
                if (HttpMethod.invalidatesCache(networkRequest.method())) {
                    try {
                        cache.remove(networkRequest);
                    } catch (IOException ignored) {
                        // The cache cannot be written.
                    }
                }
            }
            return response;
        }
    }

对缓存,这里我们使用的是全局变量 cache,它是 InternalCache 类型的变量。InternalCache 是一个接口,在 OkHttp 中只有一个实现类 Cache。在 Cache 内部,使用了 DiskLruCache 来将缓存的数据存到磁盘上。DiskLruCache 以及 LruCache 是 Android 上常用的两种缓存策略。前者是基于磁盘来进行缓存的,后者是基于内存来进行缓存的,它们的核心思想都是 Least Recently Used,即最近最少使用算法。我们会在以后的文章中详细介绍这两种缓存框架,也请继续关注我们的文章。
另外,上面我们根据请求和缓存的响应中的信息来判断是否存在缓存可用的时候用到了 CacheStrategy 的两个字段,得到这两个字段的时候使用了非常多的判断,其中涉及 Http 缓存相关的知识,感兴趣的话可以自己参考源代码。

五、ConnectInterceptor(连接复用)

连接拦截器 ConnectInterceptor 用来打开到指定服务器的网络连接,并交给下一个拦截器处理。这里我们只打开了一个网络连接,但是并没有发送请求到服务器。从服务器获取数据的逻辑交给下一级的拦截器来执行。虽然,这里并没有真正地从网络中获取数据,而仅仅是打开一个连接,但这里有不少的内容值得我们去关注。因为在获取连接对象的时候,使用了连接池 ConnectionPool 来复用连接。

    public final class ConnectInterceptor implements Interceptor {

        @Override public Response intercept(Chain chain) throws IOException {
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Request request = realChain.request();
            StreamAllocation streamAllocation = realChain.streamAllocation();

            boolean doExtensiveHealthChecks = !request.method().equals("GET");
            HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
            RealConnection connection = streamAllocation.connection();

            return realChain.proceed(request, streamAllocation, httpCodec, connection);
        }
    }

这里的 HttpCodec 用来编码请求并解码响应,RealConnection 用来向服务器发起连接。它们会在下一个拦截器中被用来从服务器中获取响应信息。下一个拦截器的逻辑并不复杂,这里万事具备之后,只要它来从服务器中读取数据即可。可以说,OkHttp 中的核心部分大概就在这里,所以,我们就先好好分析一下,这里在创建连接的时候如何借助连接池来实现连接复用的。
根据上面的代码,当我们调用 streamAllocationnewStream() 方法的时候,最终会经过一系列的判断到达 StreamAllocation 中的 findConnection() 方法。

    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        // ...
        synchronized (connectionPool) {
            // ...
            // 尝试使用已分配的连接,已经分配的连接可能已经被限制创建新的流
            releasedConnection = this.connection;
            // 释放当前连接的资源,如果该连接已经被限制创建新的流,就返回一个Socket以关闭连接
            toClose = releaseIfNoNewStreams();
            if (this.connection != null) {
                // 已分配连接,并且该连接可用
                result = this.connection;
                releasedConnection = null;
            }
            if (!reportedAcquired) {
                // 如果该连接从未被标记为获得,不要标记为发布状态,reportedAcquired 通过 acquire() 方法修改
                releasedConnection = null;
            }

            if (result == null) {
                // 尝试供连接池中获取一个连接
                Internal.instance.get(connectionPool, address, this, null);
                if (connection != null) {
                    foundPooledConnection = true;
                    result = connection;
                } else {
                    selectedRoute = route;
                }
            }
        }
        // 关闭连接
        closeQuietly(toClose);

        if (releasedConnection != null) {
            eventListener.connectionReleased(call, releasedConnection);
        }
        if (foundPooledConnection) {
            eventListener.connectionAcquired(call, result);
        }
        if (result != null) {
            // 如果已经从连接池中获取到了一个连接,就将其返回
            return result;
        }

        boolean newRouteSelection = false;
        if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
            newRouteSelection = true;
            routeSelection = routeSelector.next();
        }

        synchronized (connectionPool) {
            if (canceled) throw new IOException("Canceled");

            if (newRouteSelection) {
                // 根据一系列的 IP 地址从连接池中获取一个链接
                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 (selectedRoute == null) {
                    selectedRoute = routeSelection.next();
                }

                // 创建一个新的连接,并将其分配,这样我们就可以在握手之前进行终端
                route = selectedRoute;
                refusedStreamCount = 0;
                result = new RealConnection(connectionPool, selectedRoute);
                acquire(result, false);
            }
        }

        // 如果我们在第二次的时候发现了一个池连接,那么我们就将其返回
        if (foundPooledConnection) {
            eventListener.connectionAcquired(call, result);
            return result;
        }

        // 进行 TCP 和 TLS 握手
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
                connectionRetryEnabled, call, eventListener);
        routeDatabase().connected(result.route());

        Socket socket = null;
        synchronized (connectionPool) {
            reportedAcquired = true;

            // 将该连接放进连接池中
            Internal.instance.put(connectionPool, result);

            // 如果同时创建了另一个到同一地址的多路复用连接,释放这个连接并获取那个连接
            if (result.isMultiplexed()) {
                socket = Internal.instance.deduplicate(connectionPool, address, this);
                result = connection;
            }
        }
        closeQuietly(socket);

        eventListener.connectionAcquired(call, result);
        return result;
    }

该方法会被放置在一个循环当中被不停地调用以得到一个可用的连接。它优先使用当前已经存在的连接,不然就使用连接池中存在的连接,再不行的话,就创建一个新的连接。所以,上面的代码大致分成三个部分:

  • 判断当前的连接是否可以使用:流是否已经被关闭,并且已经被限制创建新的流;
  • 如果当前的连接无法使用,就从连接池中获取一个连接;
  • 连接池中也没有发现可用的连接,创建一个新的连接,并进行握手,然后将其放到连接池中。

在从连接池中获取一个连接的时候,使用了 Internal 的 get() 方法。Internal 有一个静态的实例,会在 OkHttpClient 的静态代码快中被初始化。我们会在 Internal 的 get() 中调用连接池的 get() 方法来得到一个连接。

从上面的代码中我们也可以看出,实际上,我们使用连接复用的一个好处就是省去了进行 TCP 和 TLS 握手的一个过程。因为建立连接本身也是需要消耗一些时间的,连接被复用之后可以提升我们网络访问的效率。那么这些连接被放置在连接池之后是如何进行管理的呢?我们会在下文中分析 OkHttp 的 ConnectionPool 中是如何管理这些连接的。

六、CallServerInterceptor(请求服务器)

服务器请求拦截器 CallServerInterceptor 用来向服务器发起请求并获取数据。这是整个责任链的最后一个拦截器,这里没有再继续调用执行链的处理方法,而是把拿到的响应处理之后直接返回给了上一级的拦截器:

    public final class CallServerInterceptor implements Interceptor {

        @Override public Response intercept(Chain chain) throws IOException {
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            // 获取 ConnectInterceptor 中初始化的 HttpCodec
            HttpCodec httpCodec = realChain.httpStream();
            // 获取 RetryAndFollowUpInterceptor 中初始化的 StreamAllocation
            StreamAllocation streamAllocation = realChain.streamAllocation();
            // 获取 ConnectInterceptor 中初始化的 RealConnection
            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);

            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);
                }
                 // 在这里写入请求体
                if (responseBuilder == null) {
                    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);
                } else if (!connection.isMultiplexed()) {
                    streamAllocation.noNewStreams();
                }
            }
            httpCodec.finishRequest();
            if (responseBuilder == null) {
                realChain.eventListener().responseHeadersStart(realChain.call());
                // 读取响应头
                responseBuilder = httpCodec.readResponseHeaders(false);
            }
            Response response = responseBuilder
                    .request(request)
                    .handshake(streamAllocation.connection().handshake())
                    .sentRequestAtMillis(sentRequestMillis)
                    .receivedResponseAtMillis(System.currentTimeMillis())
                    .build();
            // 读取响应体
            int code = response.code();
            if (code == 100) {
                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);
            if (forWebSocket && code == 101) {
                response = response.newBuilder()
                        .body(Util.EMPTY_RESPONSE)
                        .build();
            } else {
                response = response.newBuilder()
                        .body(httpCodec.openResponseBody(response))
                        .build();
            }
            // ...
            return response;
        }
    }

七、ConnectionPool

与请求的缓存类似,OkHttp 的连接池也使用一个双端队列来缓存已经创建的连接:

private final Deque<RealConnection> connections = new ArrayDeque<>();

OkHttp 的缓存管理分成两个步骤,一边当我们创建了一个新的连接的时候,我们要把它放进缓存里面;另一边,我们还要来对缓存进行清理。在 ConnectionPool 中,当我们向连接池中缓存一个连接的时候,只要调用双端队列的 add() 方法,将其加入到双端队列即可,而清理连接缓存的操作则交给线程池来定时执行。

在 ConnectionPool 中存在一个静态的线程池:

    private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
        Integer.MAX_VALUE /* maximumPoolSize */, 
        60L /* keepAliveTime */,
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(), 
        Util.threadFactory("OkHttp ConnectionPool", true));

每当我们向连接池中插入一个连接的时候就会调用下面的方法,将连接插入到双端队列的同时,会调用上面的线程池来执行清理缓存的任务:

    void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (!cleanupRunning) {
            cleanupRunning = true;
            // 使用线程池执行清理任务
            executor.execute(cleanupRunnable);
        }
        // 将新建的连接插入到双端队列中
        connections.add(connection);
    }

这里的清理任务是 cleanupRunnable,是一个 Runnable 类型的实例。它会在方法内部调用 cleanup() 方法来清理无效的连接:

    private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
            while (true) {
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

下面是 cleanup() 方法:

    long cleanup(long now) {
        int inUseConnectionCount = 0;
        int idleConnectionCount = 0;
        RealConnection longestIdleConnection = null;
        long longestIdleDurationNs = Long.MIN_VALUE;

        synchronized (this) {
            // 遍历所有的连接
            for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
                RealConnection connection = i.next();
                // 当前的连接正在使用中
                if (pruneAndGetAllocationCount(connection, now) > 0) {
                    inUseConnectionCount++;
                    continue;
                }
                idleConnectionCount++;
                // 如果找到了一个可以被清理的连接,会尝试去寻找闲置时间最久的连接来释放
                long idleDurationNs = now - connection.idleAtNanos;
                if (idleDurationNs > longestIdleDurationNs) {
                    longestIdleDurationNs = idleDurationNs;
                    longestIdleConnection = connection;
                }
            }

            if (longestIdleDurationNs >= this.keepAliveDurationNs 
                    || idleConnectionCount > this.maxIdleConnections) {
                // 该连接的时长超出了最大的活跃时长或者闲置的连接数量超出了最大允许的范围,直接移除
                connections.remove(longestIdleConnection);
            } else if (idleConnectionCount > 0) {
                // 闲置的连接的数量大于0,停顿指定的时间(等会儿会将其清理掉,现在还不是时候)
                return keepAliveDurationNs - longestIdleDurationNs;
            } else if (inUseConnectionCount > 0) {
                // 所有的连接都在使用中,5分钟后再清理
                return keepAliveDurationNs;
            } else {
                // 没有连接
                cleanupRunning = false;
                return -1;
            }
        }

        closeQuietly(longestIdleConnection.socket());
        return 0;
    }

在从缓存的连接中取出连接来判断是否应该将其释放的时候使用到了两个变量 maxIdleConnections 和 keepAliveDurationNs,分别表示最大允许的闲置的连接的数量和连接允许存活的最长的时间。默认空闲连接最大数目为5个,keepalive 时间最长为5分钟。
上面的方法会对缓存中的连接进行遍历,以寻找一个闲置时间最长的连接,然后根据该连接的闲置时长和最大允许的连接数量等参数来决定是否应该清理该连接。同时注意上面的方法的返回值是一个时间,如果闲置时间最长的连接仍然需要一段时间才能被清理的时候,会返回这段时间的时间差,然后会在这段时间之后再次对连接池进行清理。

八、总结

以上就是我们对 OkHttp 内部网络访问的源码的分析。当我们发起一个请求的时候会初始化一个 Call 的实例,然后根据同步和异步的不同,分别调用它的 execute() 和 enqueue() 方法。虽然,两个方法一个会在当前的线程中被立即执行,一个会在线程池当中执行,但是它们进行网络访问的逻辑都是一样的:通过拦截器组成的责任链,依次经过重试、桥接、缓存、连接和访问服务器等过程,来获取到一个响应并交给用户。其中,缓存和连接两部分内容是重点,因为前者涉及到了一些计算机网络方面的知识,后者则是 OkHttp 效率和框架的核心。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用Okhttp进行文件上传时,需要注意设置请求头的Content-Type为multipart/form-data。这是因为在文件上传时,需要将文件数据以多部分的形式进行传输。\[1\]你可以使用Okhttp3库来实现这个功能。首先,你需要引入Okhttp3的jar包,可以通过在pom.xml文件中添加以下依赖来引入Okhttp3库: ```xml <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.0.0</version> </dependency> ``` 接下来,你可以使用Okhttp3的RequestBody类来创建一个请求体,然后将文件数据添加到请求体中。在创建RequestBody时,你需要指定Content-Type为multipart/form-data。这样后台才能正确解析请求并接收到数据。\[2\]如果你在设置请求头的Content-Type后仍然无法成功上传文件,可能是因为你没有正确设置RequestBody的Content-Type。你可以尝试在创建RequestBody时设置Content-Type为multipart/form-data,这样可以确保请求体的Content-Type与请求头中的Content-Type一致,从而解决上传文件的问题。\[3\] #### 引用[.reference_title] - *1* *3* [通过Okhttp3 post方式上传文件 Content-Type=multipart/form-data](https://blog.csdn.net/qq_15327175/article/details/130533804)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SpringBoot2.1.x,okhttp3网络请求之MultipartFile方式上传文件,multipart/form-data表单多文件+多参数](https://blog.csdn.net/p812438109/article/details/107943319)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值