美文网首页
Android网络编程(六)OKHttp源码解析下

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

作者: zskingking | 来源:发表于2019-01-18 17:22 被阅读86次

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

    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源码解析》。明天休息,祝大家周末快乐!!

    相关文章

      网友评论

          本文标题:Android网络编程(六)OKHttp源码解析下

          本文链接:https://www.haomeiwen.com/subject/emqwrqtx.html