Android网络编程(六)OKHttp源码解析下

这篇文章我们接着上篇文章的拦截器继续描述

BridgeInterceptor

BridgeInterceptor拦截器的作用大概有三点:

  • 请求时补全header
  • 响应阶段保存Cookie
  • 响应阶段处理GZIP

源码也比较简单,加上构造方法也就才3个方法,下面我截取intercept()部分代码给大家简单描述

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();
        RequestBody body = userRequest.body();
        if (body != null) {
            //对请求头的一些信息进行补充
            MediaType contentType = body.contentType();
            if (contentType != null) {
                requestBuilder.header("Content-Type", contentType.toString());
            }
            long contentLength = body.contentLength();
            if (contentLength != -1) {
                requestBuilder.header("Content-Length", Long.toString(contentLength));
                requestBuilder.removeHeader("Transfer-Encoding");
            } else {
                requestBuilder.header("Transfer-Encoding", "chunked");
                requestBuilder.removeHeader("Content-Length");
            }
        }
        boolean transparentGzip = false;
        ...
        ...
        //启动下一个拦截器
        Response networkResponse = chain.proceed(requestBuilder.build());
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
        Response.Builder responseBuilder = networkResponse.newBuilder()
                .request(userRequest);

        //处理Gzip,由okio完成,随后将Content-Encoding和Content-Length从头中移除
        if (transparentGzip
                && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
                && HttpHeaders.hasBody(networkResponse)) {
            GzipSource responseBody = new GzipSource(networkResponse.body().source());
            Headers strippedHeaders = networkResponse.headers().newBuilder()
                    .removeAll("Content-Encoding")
                    .removeAll("Content-Length")
                    .build();
            responseBuilder.headers(strippedHeaders);
            responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
        }
        return responseBuilder.build();
    }

BridgeInterceptor是OKHttp五个内置拦截器最简单的一个,代码也比较少,并且没有任何逻辑性可言,全是按照HTTP协议来的,关于这个拦截器大家了解一下即可。

CacheInterceptor

CacheInterceptor是OKHttp中用来处理缓存的一个拦截器,完全基于HTTP对缓存进行封装,如果对HTTP缓存不太熟悉的同学可以先看我这篇文章,由于CacheInterceptor的intercept()方法比较长,所以我会把该部分源码分两部分进行分析。
在HTTP的缓存策略中,会首先判断强制缓存是否存在,我们来看OKHttp关于这部分的代码实现

 @Override
    public Response intercept(Chain chain) throws IOException {
        //读取缓存
        Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;
        long now = System.currentTimeMillis();
        //一种缓存策略,使用强制缓存和对比缓存
        // cacheResponse=null的时候代表没缓存
        // 但是cacheResponse!=null时缓存是否有效不确定,要根据判断缓存策略中的networkResponse
        // 如果networkResponse==null&&cacheResponse!=null此时缓存是有效的
        // 如果networkResponse!=null&&cacheResponse!=null此时只能证明缓存存在,但不确定是否有效
        // 进行对比缓存的验证
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;
        //如果缓存不为空,进行缓存监控
        if (cache != null) {
            cache.trackResponse(strategy);
        }
        //缓存存在,通过CacheStrategy判定缓存无效,关闭原始资源
        if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }
        // 根据缓存策略,网络不可用并且缓存不存在或者已经失效,强制返回504
        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();
        }
        ...
        ...
        ...
}

首先读取缓存,然后通过CacheStrategy获取到缓存状态和网络状态,如果此时网络不可用并且缓存不存在或者已经失效,会返回给客户端504。如果网络不可用,但缓存中存在有效数据会直接将缓存中数据返回给客户端。这是intercept()方法第一部分。

根据HTTP缓存策略,如果强制缓存不存在或者已经失效会继续判断对比缓存,下面我们来看OKHttp中对应的代码:

    @Override
    public Response intercept(Chain chain) throws IOException {
         ...
         ...
         ...
        //以下过程需要网络
        Response networkResponse = null;
        try {
            //执行下一个拦截器
            networkResponse = chain.proceed(networkRequest);
        } finally {
            if (networkResponse == null && cacheCandidate != null) {
                closeQuietly(cacheCandidate.body());
            }
        }
        // cacheResponse=null的时候代表没缓存
        // 但是cacheResponse!=null时缓存是否有效不确定,要根据判断缓存策略中的networkResponse
        // 如果networkResponse==null&&cacheResponse!=null此时缓存是有效的
        // 如果networkResponse!=null&&cacheResponse!=null此时只能证明缓存存在,但不确定是否有效
        // 进行对比缓存的验证
        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)) {
                // Offer this request to the cache.
                //将数据写入缓存中
                CacheRequest cacheRequest = cache.put(response);
                return cacheWritingResponse(cacheRequest, response);
            }
            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                }
            }
        }
        return response;
    }

如果强制缓存不存在或者无效,会直接启动下一个拦截器请求服务器进行对比缓存验证。当服务器做出响应后,OKHttp会继续判断缓存是否为空,如果不为空再进行判断服务器返回的状态码,如果为304代表缓存有效,然后会将本地缓存更新并返回给上一层。如果不满足上述两个条件:缓存不为空、缓存有效,就直接获取到服务器返回的数据,将数据保存本地后返回给上一层。

在OKHttp中使用缓存的时候需要开辟一块本地空间,也就是这段代码:

        File fileCache = new File(context.getExternalCacheDir(),"response");
        int cacheSize = 10*1024*1024;//缓存大小为10M
        Cache cache = new Cache(fileCache, cacheSize);
        //进行OkHttpClient的一些设置
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                 ...
                 ...
                .cache(cache)//设置缓存
                .build();

这段代码需要开发者自己去写,讲OKHttp使用的时候我们也提到过,就不再过多叙述。另外,OKHttp中缓存是通过DiskLruCache来实现的,DiskLruCache内部维护了一个LinkedHashMap来实现缓存淘汰算法。

关于OKHttp的缓存严格遵守HTTP缓存,熟悉HTTP缓存的同学阅读起源码应该是非常轻松。另外,源码中每行代码我基本都标有详细注释,文字部分就显得少了一些,所以我建议大家能够按着顺序结合注释去读一遍源码。

ConnectInterceptor

这个拦截器源码大概就十几行,只是创建了HttpCodec和RealConnection传递给下一个拦截器

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");
        //通过StreamAllocation创建HttpCodec和RealConnection
        HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();
        //启动下一个拦截器
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }

HttpCodec和RealConnection 在传输数据中扮演者非常重要的角色,HttpCodec可以理解为进行IO传输的stream,RealConnection代表连接内部对Socket进行了封装,二者由StreamAllocation 进行管理。下面我们来着重分析StreamAllocation 、RealConnection 、HttpCodec。

StreamAllocation

StreamAllocation 中在newStream()中完成HttpCodec的创建

 public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
        int connectTimeout = client.connectTimeoutMillis();
        int readTimeout = client.readTimeoutMillis();
        int writeTimeout = client.writeTimeoutMillis();
        boolean connectionRetryEnabled = client.retryOnConnectionFailure();
        try {
            //获取连接
            RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                    writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
            //根据获取到的连接创建HttpCodec
            HttpCodec resultCodec = resultConnection.newCodec(client, this);
            synchronized (connectionPool) {
                codec = resultCodec;
                return resultCodec;
            }
        } catch (IOException e) {
            throw new RouteException(e);
        }
    }

首先调用findHealthyConnection()可以获取到一个RealConnection 对象,然后通过该RealConnection对象创建 HttpCodec 对象。来看一下获取连接的findHealthyConnection()方法

 private RealConnection findHealthyConnection(......) {
        while (true) {
            RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
                    connectionRetryEnabled);
            synchronized (connectionPool) {
                // successCount代表该连接执行任务的此时,
                // 如果是一个新连接就直接拿来使用
                if (candidate.successCount == 0) {
                    return candidate;
                }
            }
            //如果连接不可用
            if (!candidate.isHealthy(doExtensiveHealthChecks)) {
                //将改连接进行回收
                noNewStreams();
                continue;
            }
            return candidate;
        }
    }

内部是一个无限的while()循环,通过调用findConnection()方法获取连接,获取到连接后判断该连接是否是一个新连接,说到这可能有些同学会有疑问,连接怎么还有新旧之分呢?这里先简单说一下,OKHttp中通过维护一个连接处来实现连接的复用,所以findConnection()获取到的连接可能是直接从连接池中获取到的。接着上面说,如果是一个新连接就直接将该连接返回然后结束循环,如果不是一个新连接会再次判断该连接是否可用,如果不可用就将该连接回收随后跳出本次循环进行下次循环,如果可用就返回该连接随后跳出循环。下面来看一下findConnection()源码:
findConnection()源码较长,所以我就分开进行描述

private RealConnection findConnection(......) {
        //声明一个路由
        Route selectedRoute;
        synchronized (connectionPool) {
            // 首先使用已存在的连接
            RealConnection allocatedConnection = this.connection;
            //noNewStreams为true的时候代表该连接不可以创建流对象
            if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
                return allocatedConnection;
            }
           //从连接池中去连接,将获取到的连接赋值给this的connection
            Internal.instance.get(connectionPool, address, this, null);
            if (connection != null) {
                return connection;
            }
            selectedRoute = route;
        }
}

首先声明一个路由Route ,然后判断当前对象中是否存在连接,如果存在并且该连接的noNewStreams值为false就直接返回,否则会从连接池中获取连接,
Internal.instance.get(connectionPool, address, this, null)这句代码大致意思就是从连接池中获取address对应的连接,如果获取到就讲连接赋值给this,也就是当前StreamAllocation对象,如果没有从连接池中获取到连接就执行如下步骤:

private RealConnection findConnection(......) {
        ...
        ...
        // 重新选一个路由,多IP支持
        if (selectedRoute == null) {
            selectedRoute = routeSelector.next();
        }
        //能执行到这说明connection为null,说明从连接池中没有取到合适的连接
        RealConnection result;
        synchronized (connectionPool) {
            //如果已经取消请求抛出异常
            if (canceled) throw new IOException("Canceled");
            // 拿着新路由再次去连接池中找连接
            Internal.instance.get(connectionPool, address, this, selectedRoute);
            if (connection != null) {
                route = selectedRoute;
                return connection;
            }
            route = selectedRoute;
            refusedStreamCount = 0;
            //以上条件都不符合,创建一个连接
            result = new RealConnection(connectionPool, selectedRoute);
            //将StreamAllocation对象添加到connection的StreamAllocation集合中
            //表示该StreamAllocation对象使用过自己
            acquire(result);
        }
        //拿到连接对象后建立socket连接
        result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
        ...
        ...
}

选择下一个路由,这里简单说下路由这个概念,有些服务器一个域名是可以对应多个IP的,如果存在对IP,请求DNS服务器时会返回多个IP。代码中通过routeSelector.next()来选择一个路由,然后拿着selectedRoute后会重新去连接池中找合适的连接,如果找到了直接将连接返回否则会创建一个新的连接,创建完毕后将StreamAllocation添加到connect对象中的StreamAllocation集合中,用来表示使用过自己的StreamAllocation对象。以上步骤只是获取到了connetc对象并未产生真正的连接,所以还需要调用connect的connect()方法进行socket连接的建立。连接完成之后还需要将连接对象加入到连接池中,下面我们来看实现步骤:

private RealConnection findConnection(......) {
        ...
        ...
        //新建的连接肯定可以用,所以将该连接移出黑名单
        routeDatabase().connected(result.route());
        Socket socket = null;
        synchronized (connectionPool) {
            //将连接加入到连接池当中
            Internal.instance.put(connectionPool, result);  
            //如果同时创建到同一地址的另一个多路复用连接,则释放该连接
            if (result.isMultiplexed()) {
                socket = Internal.instance.deduplicate(connectionPool, address, this);
                result = connection;
            }
        }
        closeQuietly(socket);
        return result;
    }

首先将新连接从routeDatabase路由黑名单中移除,然后将连接加入到连接池,下面会判断该地址是否已经存在重复的socket连接,如果存在将重复的socket连接关闭。
以上内容就是StreamAllocation创建HttpCodec和RealConnection的流程,另外,在StreamAllocation内部也可以通过调用cancel()、release()来实现取消请求,释放连接。StreamAllocation主要内容差不多就是这些,下面我们来研究一下连接池ConnectionPool内部原理

ConnectionPool

声明:由于对连接类RealConnection某些地方理解不太到位,为了避免误人子弟,我先不对其进行源码分析,等整明白了再补上吧。

先来看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));

   private final int maxIdleConnections;//存储的最大连接数
   private final long keepAliveDurationNs;//闲置的连接存活的时间

   //存储连接的集合
   private final Deque<RealConnection> connections = new ArrayDeque<>();

   public MyConnectionPool(int maxIdleConnections, long keepAliveDuration,             
   TimeUnit timeUnit) {
        this.maxIdleConnections = maxIdleConnections;
        this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
        // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
        if (keepAliveDuration <= 0) {
            throw new IllegalArgumentException("keepAliveDuration <= 0: " + 
    keepAliveDuration);
        }
    }

指定了缓存中最大连接数和闲置连接的存活时间,默认值分别是:5个、5分钟。同时内部维护了一个静态类型的线程池,该线程池的作用是用来清理失效的连接。我们首先来看连接入队操作:

    //往连接池中添加连接
    void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        //没添加一次都会判断清理无效连接的线程是否正在工作
        //如果没有就开启清理线程
        if (!cleanupRunning) {
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }

在每一次进行put()操作的时候都会试图开启清理连接线程池,随后将连接加入到connections中。来看一下清理连接的线程任务cleanupRunnable:

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 (MyConnectionPool.this) {
                        try {
                            MyConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

内部逻辑很简单,调用了cleanup()方法进行清理操作,如果cleanup()返回值waitNanos 值为-1继续清理操作,如果大于0进行wait()操作,waitNanos 值的含义我会在下面详细解析。来看一下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++;
                // If the connection is ready to be evicted, we're done.
                long idleDurationNs = now - connection.idleAtNanos;
                //当前连接超过最大闲置时间
                if (idleDurationNs > longestIdleDurationNs) {
                    longestIdleDurationNs = idleDurationNs;
                    longestIdleConnection = connection;
                }
            }
            //超出空闲时间||闲置连接超出最大闲置连接
            if (longestIdleDurationNs >= this.keepAliveDurationNs
                    || idleConnectionCount > this.maxIdleConnections) {
                //对闲置时间超过keepAliveDurationNs的连接进行清除
                connections.remove(longestIdleConnection);
            } else if (idleConnectionCount > 0) {
                // 存在闲置的连接,但还未超出keepAliveDurationNs,
                // 返回下次需要执行清理的等待时间
                return keepAliveDurationNs - longestIdleDurationNs;
            } else if (inUseConnectionCount > 0) {
                //没有空闲的连接,让清理线程等待keepAliveDuration之后再次执行
                return keepAliveDurationNs;
            } else {
                //不存在任何连接,清理结束,并结束清理线程
                cleanupRunning = false;
                return -1;
            }
        }
        closeQuietly(longestIdleConnection.socket());
        //执行完一个空闲连接后返回0,代表不等待立即清理下一个
        return 0;
    }
  • 开启一个for()循环,计算当前闲置连接和正在使用连接的数量,并记录下一来个超出最大闲置时间的连接
  • 当前连接超出最大空闲时间、当前闲置连接数超出最大闲置连接数,两个条件满足其一就进行清理操作,然后返回0立即进行下一次清理
  • 如果存在闲置连接,但未超出最大闲置时间,则通知线程等待一定的时间后再开启清理操作
  • 没有闲置的连接,通知线程等待keepAliveDurationNs时间后再次开启清理操作
  • 以上条件都不满足代表即代表不存在任何连接,直接返回-1结束清理任务

下面我们来看获取连接操作:

    RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
            //存在传入地址的连接
            if (connection.isEligible(address, route)) {
                streamAllocation.acquire(connection);
                return connection;
            }
        }
        return null;
    }

获取连接操作也比较简单,拿着传入的address和route进行比较, 比较成功代表该连接可能符合复用要求直接返回。

ConnectionPool主要内容大概就是这些,入队和出队操作都比较好理解,只有清理操作比较复杂但是逻辑也很清晰,也不难。

HttpCodec

HttpCodec是一个接口,OKHttp为其提供了两个实现类,分别是Http1Codec、Http2Codec对应HTTP1和HTTP2,内部就是一些数据的读取写入,本篇文章就不再进行叙述,感兴趣的同学可自行了解下。

CallServerInterceptor

CallServerInterceptor是最后一个拦截器,它的作用就是实现数据在网络上传输,OKHttp中数据传输是通过连接RealConnection和流HttpCodec实现的,而这两个对象在上一个拦截器已经常见完毕,所以CallServerInterceptor中只需要数据读写即可,下面我们来分析intercept()源码:

        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        HttpCodec httpCodec = realChain.httpStream();
        StreamAllocation streamAllocation = realChain.streamAllocation();
        RealConnection connection = (RealConnection) realChain.connection();
        Request request = realChain.request();

获取到上个拦截器传来的对象

 httpCodec.writeRequestHeaders(request);//写入请求头

首先通过httpCodec将请求头信息写入

  Response.Builder responseBuilder = null;
        //请求体为空做如下步骤
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
            //在发送主体前先询问服务器,是否处理post数据,如果处理就上传主体,反之不上传
            //实际应用中,请求体比较大时 才会用到100-continue协议
            if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
                httpCodec.flushRequest();
                responseBuilder = httpCodec.readResponseHeaders(true);
            }
            //如果responseBuilder为null代表服务区可接受post数据,此时将body写入
            if (responseBuilder == null) {
                // Write the request body if the "Expect: 100-continue" expectation was met.
                Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
                BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
                //写入请求体
                request.body().writeTo(bufferedRequestBody);
                //关闭写入流
                bufferedRequestBody.close();
            } else if (!connection.isMultiplexed()) {//如果是HTTP1就关闭流

                streamAllocation.noNewStreams();
            }
        }

这部分代码是请求体的写入,大概有三个判断,如下:

  • 判断请求头中是否存在Expect:100-continue,如果存在说明需要先询问服务器是否接受post请求体
  • 如果responseBuilder为null代表服务器可接收post请求体,可以通过httpCodec获取到输出流Sink将数据写入
  • 如果服务器不接收post请求体并且http版本为http1就将连接关闭

以上步骤为请求步骤,下面来看响应步骤

       //请求结束
        httpCodec.finishRequest();
        //获取响应头
        if (responseBuilder == null) {
            responseBuilder = httpCodec.readResponseHeaders(false);
        }
        //构建响应
        Response response = responseBuilder
                .request(request)
                .handshake(streamAllocation.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();
        //读取响应码
        int code = response.code();
        if (forWebSocket && code == 101) {
            // 连接正在升级,随意构建一个非null响应体
            response = response.newBuilder()
                    .body(Util.EMPTY_RESPONSE)
                    .build();
        } else {
            //读取响应体
            response = response.newBuilder()
                    .body(httpCodec.openResponseBody(response))
                    .build();
        }
        ...
        ...
        return response;
  • 首先将请求结束
  • 获取响应头
  • 构建响应结构
  • 读取状态码,如果状态码为101代表没有响应体,然后构建一个响应体为null的response
  • 如果状态码不为101就读取响应体
  • 将response返回给上一个拦截器

CallServerInterceptor的作用大概就是这些

关于OKHttp的源码分析差不多就是这些了,主要描述了请求/响应体、同/异步请求、连接池、以及5个拦截器,基本涵盖了OKHttp所有内容,仅剩Okio而这部分内容封装在另一个包中,提供了Sink和Source来负责流的读写,这部分内容在本系列文章中也不多做叙述,感兴趣的同学可参考其他文章。

总结

OKHttp源码部分分析完毕。通过本篇文章也发现了很多自己的不足,本来是要分析RealConnection这个类的,但是对其源码某些地方理解不太到位,其实具体也可以说是对Socket一些细节理解不太到位,害怕把大家带跑偏就没有对RealConnection做出分享,但我相信我会把它整明白的。如果大家发现文章中存在描述不当的地方还望能够及时指出,也让我能够及时发现问题解决问题,在此事先谢过。好了,本篇文章就到这了,下篇文章《Volley源码解析》。明天休息,祝大家周末快乐!!

猜你喜欢

转载自blog.csdn.net/weixin_34107955/article/details/86858022