OkHttp源码解析

一、请求流程

通过OkHttpClient将构建的Request转换为Call,然后再RealCall中进行同步、异步任务。最后通过一系列拦截器interceptors发出网络请求并得到返回的response。
在这里插入图片描述首先要创建OkHttpClient对象:

//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();

在OkHttpClient的构造方法中,调用了new Builder()方法:

/**
 * 这是个快捷、便利的操作,全部使用默认配置
 */
public OkHttpClient() {
  this(new Builder());
}

为了方便我们使用,提供了一个“快捷操作”。OkHttp为我们提供了默认的配置,OkHttpClient.Builder类的成员有很多:

/**
 * 这是个快捷、便利的操作,全部使用默认配置
 */
public Builder() {
  dispatcher = new Dispatcher(); // 分发器
  protocols = DEFAULT_PROTOCOLS; // 协议
  connectionSpecs = DEFAULT_CONNECTION_SPECS; // 传输层版本和连接协议
  proxySelector = ProxySelector.getDefault(); // 代理选择
  cookieJar = CookieJar.NO_COOKIES; // cookie
  socketFactory = SocketFactory.getDefault(); // socket工厂
  hostnameVerifier = OkHostnameVerifier.INSTANCE; // 主机名字确认
  certificatePinner = CertificatePinner.DEFAULT; // 证书链
  proxyAuthenticator = Authenticator.NONE; // 代理身份验证
  authenticator = Authenticator.NONE; // 本地身份验证
  connectionPool = new ConnectionPool(); //连接池
  dns = Dns.SYSTEM; // 域名
  followSslRedirects = true; // 安全套接层重定向
  followRedirects = true; // 本地重定向
  retryOnConnectionFailure = true; // 重试连接失败
  connectTimeout = 10_000; // 连接超时
  readTimeout = 10_000; // read超时
  writeTimeout = 10_000; // write超时
}

从这可以看出几乎所有用到的类都和OkHttpClient有关系,可以由此通过builder()来设置一些参数(建造者模式实现的)。如果不设置,就会采用默认的配置。

OkHttpClient实现了Call.Factory,专门负责根据新来的请求创建Call(CallFactory负责创建HTTP请求,HTTP请求被抽象成了OkHttp3.Call类,它表示一个已经准备好且可随时执行的HTTP请求)。

/**
 * Call.Factory接口
 */
interface Factory {
  Call newCall(Request request);
}

创建完OkHttpClient对象后要发起post请求:

//post请求
  Request request = new Request.Builder()
      .url(url)
      .build();
Response response = client.newCall(request).execute();
return response.body().string();

在Retrofit中,callFactory专门负责创建HTTP请求,HTTP请求实际上就是OkHttp的okhttp3.Call类,表示已准备好随时可以执行的HTTP请求。

/**
 * OkHttpClient#newCall
 * RealCall类负责创建Call,准备将要执行的request
 */
@Override public Call newCall(Request request) {
  return new RealCall(this, request);
}

通过上面的代码,可以看出“功劳”全在RealCall类了(可以说RealCall是真正的请求执行者)。查看RealCall的构造方法,一共传递了两个参数:OkHttpClient对象和创建的Request。

二、同步、异步网络请求

2.1 同步网络请求

Response response = client.newCall(request).execute();

/**
* Prepares the {@code request} to be executed at   some point in the future.
*/
@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

// RealCall为真正的请求执行者
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

先看看同步网络请求RealCall#execute,主要做了4件事:

/**
 * OkHttpClient实现了Call.Factory接口来创建新的Call,
 * 通过RealCall类。
 * dispatcher只是告诉它我们的执行状态:开始执行调executed、执行完毕调finished
 */
@Override public Response execute() throws IOException {
  synchronized (this) {
    //1.检查这个Call是否被执行了
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  try {
    //2.获取到dispatcher对象,执行executed方法将当前RealCall加入到双端队列中
    client.dispatcher().executed(this);
    //3.获取HTTP的响应Response
    Response result = getResponseWithInterceptorChain(false);
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    //4.将执行状态告诉dispatcher  
    client.dispatcher().finished(this);
  }
}

4板斧:

  • ①检查这个Call是否已经被执行了。每个call只能被执行一次,只能通过Call#clone方法克隆出一个完全一样的call
  • ②利用client.dispatcher().executed(this)方法实际执行,其中dispatcher是OkHttpClient构造Builder中的成员,调用dispatcher的executed方法将当前RealCall加入到双端队列中
  • ③调用getResponseWithInterceptorChain()方法获取HTTP的返回结果,这其中还会进行拦截操作
  • ④最后通知dispatcher自己已经执行完毕了

这里会用client对象(上面创建RealCall时传入的OkHttpClient)的dispatcher()方法获取Dispatcher对象,并调用其executed()方法将当前的RealCall加入到双端队列中。涉及到Dispatcher的部分只是告知它执行状态:调用executed()方法开始执行了、调用finished()方法执行完毕了。

对于executed()方法的定义,这里的runningSyncCalls类型是Deque< RealCall >:

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

把RealCall加入到双端队列后,这里发送网络请求、解析返回结果的还是getResponseWithInterceptorChain()方法:

Response getResponseWithInterceptorChain() throws IOException {
  // 添加一系列的拦截器,注意添加顺序
  List<Interceptor> interceptors = new ArrayList<>();
  // 配置OkHttpClient时设置的interceptors
  interceptors.addAll(client.interceptors());
  // 负责失败重试以及重定向的RetryAndFollowUpInterceptor
  interceptors.add(retryAndFollowUpInterceptor);
  // 把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好响应的桥拦截器
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  // 缓存拦截器,从缓存中拿数据
  interceptors.add(new CacheInterceptor(client.internalCache()));
  // 网络连接拦截器,建立网络连接
  interceptors.add(new ConnectInterceptor(client));
  if (!retryAndFollowUpInterceptor.isForWebSocket()) {
    // 配置OkHttpClient时设置的networkInterceptors  
    interceptors.addAll(client.networkInterceptors());
  }
  // 服务器请求拦截器,向服务器发起请求获取数据、从服务器读取响应数据
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));
  // 构建一条责任链    
  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  // 处理责任链,开始链式调用    
  return chain.proceed(originalRequest);
}

在创建了一个列表对象后,把client中的拦截器、重连拦截器、桥拦截器、缓存拦截器、网络连接器和服务器连接器依次加入到列表中。随后利用这个列表对象,创建了一个拦截器链。这里采用了责任链模式(每当一个拦截器执行完毕后会调用下一个拦截器或不调用并返回结果),最终拿到的响应就是这个责任链执行完毕后返回的结果。当然,我们也可以自定义一个拦截器,加入到这个拦截器链中。

调用的RealInterceptorChain()方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    //如果我们已经有一个stream。确定即将到来的request会使用它
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    //如果我们已经有一个stream, 确定chain.proceed()唯一的call
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    //调用链的下一个拦截器
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

主要做了以下三件事:

  • ①实例化下一个拦截器对应的RealIterceptorChain对象,该对象会传递给当前的拦截器
  • ②得到当前的拦截器:interceptors是存放拦截器的ArrayList
  • ③调用当前拦截器的intercept()方法,并将下一个拦截器的RealIterceptorChain对象传递下去。

对于上述请求的流程图:
在这里插入图片描述

2.2 异步网络请求

接下来再看看异步网络请求RealCall#enqueue:

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }
});


void enqueue(Callback responseCallback, boolean forWebSocket) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

//Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
  //如果runningAsyncCalls不满且call占用的host < 最大数量 
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call); // 将call加入到runningAsyncCalls中
    executorService().execute(call); // 利用线程池执行Call
  } else {
    //否则就将其扔到readyAsyncCalls队列中  
    readyAsyncCalls.add(call);
  }
}

可以看出Dispatcher在异步网络请求中发挥了作用,如果当前还能执行一个并发请求的话,那就立即执行。否则就加入到readyAsyncCalls队列中。
如果执行ing的请求执行完毕后,会调用promoteCalls()方法,将readyAsyncCalls队列中的AsyncCall“升级”为runningAsyncCalls,并开始执行。

下面是异步请求的时序图:
在这里插入图片描述
关于runningAsyncCalls和readyAsyncCalls:

/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //正在准备中的异步请求队列

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //运行中的异步请求

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); //同步请求

AsyncCall是RealCall的内部类,实现了Runnable接口,所以才能被提交到ExecutorService上执行。
Call被加入到线程池中执行了,看RealCall的内部类AsyncCall:

//异步请求
    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;

        private AsyncCall(Callback responseCallback) {
            super("OkHttp %s", redactedUrl());
            this.responseCallback = responseCallback;
        }

        String host() {
            return originalRequest.url().host();
        }

        Request request() {
            return originalRequest;
        }

        RealCall get() {
            return RealCall.this;
        }

        @Override protected void execute() {
            boolean signalledCallback = false;
            try {
                Response response = getResponseWithInterceptorChain();
                if (retryAndFollowUpInterceptor.isCanceled()) {
                    signalledCallback = true;
                    responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                } else {
                    signalledCallback = true;
                    responseCallback.onResponse(RealCall.this, response);
                }
            } catch (IOException e) {
                if (signalledCallback) {
                    // Do not signal the callback twice!
                    Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
                } else {
                    responseCallback.onFailure(RealCall.this, e);
                }
            } finally {
                client.dispatcher().finished(this);
            }
        }
    }

AsyncCall中的execute()方法在执行时会调用getResponseWithInterceptorChain()方法,并将response通过responseCallback return给上层使用者。因此,异步请求enqueue()通过Dispatcher利用ExecutorService实现。同步、异步请求都是在getResponseWithInterceptorChain方法中通过Interceptor链来实现网络请求逻辑

三、分发器Dispatcher

Dispatcher这个类的作用就是对请求进行分发,但是同步请求无法体现出Dispatcher的作用。因为我们最初是创建一个RealCall将其加入到双端队列runningSyncCalls中,同步请求会在当前线程中立即getResponseWithInterceptorChain()执行,执行完毕后又会调用Dispatcher的finished(RealCall)方法将该请求从队列中移除。

还有异步请求,拿到RealCall后调用它的enqueue(Callback responseCallback)方法,并设置一个回调:

client.dispatcher().enqueue(new AsyncCall(responseCallback));

AsyncCall间接继承Runnable,run方法会执行调用AsyncCall的execute()方法。和RealCall的execute()类似,都是使用责任链来完成一个网络请求。

当调用Dispatcher的enqueue(AsyncCall)方法时,也会将AsyncCall加入到一个队列中,并会在请求执行完毕后从该队列中移除。这里的双端队列指的是runningAsyncCalls或readyAsyncCalls,用来存储异步类型的请求。runningAsyncCalls是正在执行的队列,当正在执行的队列达到上限后,就会将其安置在就绪队列readyAsyncCalls中:

//Dispatcher#enqueue方法
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

当把请求加入到runningAsyncCalls后,会立即使用一个线程池来执行该AsyncCall。如此这个请求的责任链就会在下一个线程池中被异步执行了,这个线程池由executorService()方法返回:

//Dispatcher#executorService方法
public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}

可以看出,当线程池不存在时就要去创建一个线程池。当然也可以在构建OkHttpClient时,自定义一个Dispatcher,并在其构造方法中为其制定一个线程池。

通过上面异步请求时序图中关于分发器Dispatcher的逻辑,可以看出实际请求的执行过程并不在此完成,这里只能决定在哪个线程中执行请求,并把请求用双端队列缓存下来。实际请求的执行过程是在责任链完成的。

四、责任链执行过程

在创建责任链时,传入的第5个参数index为0,会被赋值给实例内部的同名全局变量:

// 构建一条责任链 
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, 
    originalRequest, this, eventListener, client.connectTimeoutMillis(), 
    client.readTimeoutMillis(), client.writeTimeoutMillis());

当启用责任链时,会调用它的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可以从拦截器列表中取出一个拦截器,并调用inercept()方法,把下一个执行链作为参数传进去。这样当一个拦截器希望自己的下一级拦截器能够继续处理这个请求时,可以调用传入的责任链的proceed()方法。如果自己已经处理完毕,且不需要下级拦截器继续处理时,直接返回一个Response实例即可。

责任链的执行过程:
在这里插入图片描述清楚OkHttp的拦截器链执行过程,看一下各个拦截器都是干什么的。

4.1 重试和重定向RetryAndFollowUpInterceptor

主要是用来请求失败后的重试,以及某些情况下的重定向。责任链会在进行处理的时候调用第一个拦截器的intercept()方法,假如没有添加自定义拦截器,那么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对象相当于是个管理类,负责维护服务器连接、并发流和请求之间的关系。StreamAllocation还会初始化一个Socket连接对象,获取输入、输出流对象。
通过client.connectionPool()方法传入一个连接池对象ConnectionPool,在当前方法中并没有实际用到,而是将其传递到下面的拦截器,来从服务器中获取请求的响应。

4.2 桥拦截器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();
        }
    }

4.3 缓存拦截器CacheInterceptor

建立连接前要根据请求的信息和缓存的响应信息(request),判断cache中是否存在缓存可用。如果有可用缓存(且有效),就将缓存返回给用户。否则就继续责任链来从服务器中获取响应。当获取到响应的时候,又会把响应缓存到磁盘上(需要比较本地CacheResponse和网络networkResponse比较,决定是否update CacheResponse)。否则不存在CacheResponse,直接将networkResponse缓存
是OkHttp内部封装了一个Cache类,利用DiskLruCache对磁盘上的有限空间进行缓存使用,按照LRU算法进行缓存淘汰。
在构造OkHttpClient时设置Cache对象,在其构造中可以指定缓存目录和大小:

public Cache(File directory, long maxSize);
    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;
        }
    }

对缓存,使用的是InternalCache类型的全局变量cache。InternalCache是一个接口,在OkHttp中只有一个实现类Cache。在Cache内部,使用DiskLruCache将缓存的数据存储到磁盘上。
根据请求和缓存响应的信息来判断是否有可用缓存时,用到了CacheStrategy的两个字段,期间使用了很多的判断。

4.4 连接拦截器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来进行编码请求和解码响应,建立连接就是创建HttpCodec对象,该过程涉及了StreamAllocation、RealConnection。RealConnection用来向服务器发起连接,它们会在下一个拦截器中从服务器中获取响应信息(下一个拦截器只需要从服务器中读取数据,OkHttp的核心部分)。当我们调用streamAlloction的newStream()方法时,最终会经过一系列的判断到达StreamAlloction的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()方法来得到一个连接。

4.5 服务器请求拦截器CallServerInterceptor

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

    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;
            // 检查请求方法,用HttpCodec处理request
            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) {
                // 进行网络请求得到response
                responseBuilder = httpCodec.readResponseHeaders(false);
                response = responseBuilder
                        .request(request)
                        .handshake(streamAllocation.connection().handshake())
                        .sentRequestAtMillis(sentRequestMillis)
                        .receivedResponseAtMillis(System.currentTimeMillis())
                        .build();
                code = response.code();
            }
            realChain.eventListener().responseHeadersEnd(realChain.call(), response);
            if (forWebSocket && code == 101) {
                response = response.newBuilder()
                        .body(Util.EMPTY_RESPONSE)
                        .build();
            } else {
                response = response.newBuilder()
                        .body(httpCodec.openResponseBody(response))
                        .build();
            }
            // 返回response
            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));

每次向连接池中添加一个连接时都会调用以下put()方法:

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

将连接插入到双端队列的同时,还会调用线程池来清理缓存。清理任务是cleanupRunnable,会在方法内部调用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(最大允许的闲置的连接数量,默认5个)和keepAliveDurationNs(允许连接的最长存活时间,最长5分钟)。

上面的方法会遍历缓存中的连接,试图寻找一个闲置时间最长的连接,然后根据该连接的闲置时间、最大允许的连接数等参数来决定是否应该清理该连接。以上方法返回的是一个时间,如果闲置时间最长的连接不满足清理要求,会返回该段时间的时间差,然后在这个时间过后在此对连接池进行清理。

总结

当发起请求时会初始化一个Call实例,然后根据同步、异步,分别调用execute和enqueue方法。虽然它们一个在当前线程被立即执行、一个会在线程池中执行,但是它们进行网络请求的逻辑都是一样的:通过拦截器组成责任链,一次经过重试、桥接、缓存、连接和访问服务器,来获取到一个响应返回给用户。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值