美文网首页
OKHttp源码详解

OKHttp源码详解

作者: 蒋帅Android | 来源:发表于2017-12-01 19:53 被阅读0次

    首先大概看一下整个流程图:接下来讲解的思路将根据这个流程来。

    okhttp_full_process.png
    一、创建一个OkHttpClient对象:
        OkHttpClient client = new OkHttpClient();  
    

    二、怎么不见Builder呢?
    看下OkHttpClient的构造函数,其中new Builder(),这里采用了默认配置。OkHttpClient.Builder成员有很多

    public OkHttpClient() {  
      this(new Builder());  
    } 
    
    public Builder() {  
      dispatcher = new Dispatcher();  
      protocols = DEFAULT_PROTOCOLS;  
      connectionSpecs = DEFAULT_CONNECTION_SPECS;  
      proxySelector = ProxySelector.getDefault();  
      cookieJar = CookieJar.NO_COOKIES;  
      socketFactory = SocketFactory.getDefault();  
      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;  
      writeTimeout = 10_000;  
    } 
    

    三、发起Http请求:
    3.1先看一下HTTP的同步请求:

    String run(String url) throws IOException {  
      Request request = new Request.Builder()  
          .url(url)  
          .build();  
      
      Response response = client.newCall(request).execute();  
      return response.body().string(); 
    

    OkHttpClient实现了Call.Factory,负责为Request创建新的Call。

    现在来看一下它是如何创建一个 new Call的:

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

    查看它的源码知它最后调用了RealCall();

    网络同步请求:RealCall.execute()源码:

    @Override public Response execute() throws IOException {  
      synchronized (this) {  
        if (executed) throw new IllegalStateException("Already Executed");  // (1)  
        executed = true;  
      }  
      try {  
        client.dispatcher().executed(this);                                 // (2)  
        Response result = getResponseWithInterceptorChain();                // (3)  
        if (result == null) throw new IOException("Canceled");  
        return result;  
      } finally {  
        client.dispatcher().finished(this);                                 // (4)  
      }  
    } 
    

    这里面做了4件事情

    1)首先if检查这个call是否已经完全执行了,每个call只能执行一次,如果想要一个的完全一样的call,可以利用call.clone方法进行克隆。

    2)利用client.dispatcher().executed(this) 来进行实际执行。这里出现了dispatcher,它也是OkHttpClient.Builder的成员之一。在同步的流程中,它的作用只是告知OkHttpClient的执行状态,比如是开始执行了(调用executed),比如执行完毕了(调用了finished)。

    3)掉用getResponseWithInterceptorChain()函数来获取HTTP返回结果。

    4)最后还要通知dispatcher自己已经执行完毕。

    3.1.1 getResponseWithInterceptorChain()
    然后先重点看一下getResponseWithInterceptorChain()的源码:

        private Response getResponseWithInterceptorChain() throws IOException {  
          // Build a full stack of interceptors.  
          List<Interceptor> interceptors = new ArrayList<>();  
          interceptors.addAll(client.interceptors());  
          interceptors.add(retryAndFollowUpInterceptor);  
          interceptors.add(new BridgeInterceptor(client.cookieJar()));  
          interceptors.add(new CacheInterceptor(client.internalCache()));  
          interceptors.add(new ConnectInterceptor(client));  
          if (!retryAndFollowUpInterceptor.isForWebSocket()) {  
            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);  
        }  
    

    可以看到在getResponseWithInterceptorChain()函数中new List<Interceptor>集合,它把实际的网络请求、缓存、透明压缩等功能都统一起来了,每一个功能都只是一个Interceptor,它们再连接成一个Interceptor.Chain,环环相扣,最后圆满完成一次网络请求。

    从 getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:


    okhttp_interceptors.png
    1. 在配置 OkHttpClient 时设置的 interceptors;
    2. 负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
    3. 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor;
    4. 负责读取缓存直接返回、更新缓存的 CacheInterceptor;
    5. 负责和服务器建立连接的 ConnectInterceptor;
    6. 配置 OkHttpClient 时设置的 networkInterceptors;
    7. 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。
      责任链模式在这个 Interceptor 链条中得到了很好的实践。对于把 Request 变成 Response 这件事来说,每个 Interceptor 都可能完成这件事,所以我们循着链条让每个 Interceptor 自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个 Interceptor)。这样一来,完成网络请求这件事就彻底从RealCall 类中剥离了出来,简化了各自的责任和逻辑。
      再简单分析一下ConnectIntercetor和CallServerInterceptor,看看OkHttp是怎么进行和服务器的实际通信的。

    3.1.2 ConnectInterceptor.intercept():

    @Override public Response intercept(Chain chain) throws IOException {  
    RealInterceptorChain realChain = (RealInterceptorChain) chain;  
    Request request = realChain.request();  
    StreamAllocation streamAllocation = realChain.streamAllocation();  
    
    // We need the network to satisfy this request. Possibly for validating a conditional GET.  
    boolean doExtensiveHealthChecks = !request.method().equals("GET");  
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);  
    RealConnection connection = streamAllocation.connection();  
    
    return realChain.proceed(request, streamAllocation, httpCodec, connection);  
    } 
    

    它实际上就是创建一个HttpCodec对象,它是对Http协议的操作的抽象,有两个实现Http1Codec和Http2Codec,分别对应HTTP/1和HTTP/2的版本实现。

    在Http1Codec中,它利用Okio对socket的读写操作进行封装。Okio简单理解就是:对java.io和java.nio进行了封装,让我们更便捷高效的进行IO操作。这个HttpCodec供后续步骤使用。

    3.1.3 CallServerInterceptor.intercept():

        @Override public Response intercept(Chain chain) throws IOException {  
          HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();  
          StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();  
          Request request = chain.request();  
          
          long sentRequestMillis = System.currentTimeMillis();  
          httpCodec.writeRequestHeaders(request);  
          
          if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {  
            Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());  
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);  
            request.body().writeTo(bufferedRequestBody);  
            bufferedRequestBody.close();  
          }  
          
          httpCodec.finishRequest();  
          
          Response response = httpCodec.readResponseHeaders()  
              .request(request)  
              .handshake(streamAllocation.connection().handshake())  
              .sentRequestAtMillis(sentRequestMillis)  
              .receivedResponseAtMillis(System.currentTimeMillis())  
              .build();  
          
          if (!forWebSocket || response.code() != 101) {  
            response = response.newBuilder()  
                .body(httpCodec.openResponseBody(response))  
                .build();  
          }  
          
          if ("close".equalsIgnoreCase(response.request().header("Connection"))  
              || "close".equalsIgnoreCase(response.header("Connection"))) {  
            streamAllocation.noNewStreams();  
          }  
          
          // 省略部分检查代码  
          
          return response;  
        }  
    

    该部分源码 抓住主干分为4个部分:
    1)向服务器发送request header;
    2)如果有request body,就向服务器发送;
    3)读取response header,先构造一个response对象;(这一步用到httpCodec)
    4)如果有response body,就在3的基础上加上body构造一个新的response对象。

    Ok,到这里我们可以看出,客户端和服务器的实际通信,核心工作都由HttpCodec对象完成的,而HttpCodec实际上利用的是Okio,而Okio实际上还是用的socket。

    3.2 发起异步网络请求:
    client.newCall(request).enqueue(new CallBack(...))

        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());  
            }  
        });  
          
        // RealCall#enqueue  
        @Override public void enqueue(Callback responseCallback) {  
          synchronized (this) {  
            if (executed) throw new IllegalStateException("Already Executed");  
            executed = true;  
          }  
          client.dispatcher().enqueue(new AsyncCall(responseCallback));  
        }  
          
        // Dispatcher#enqueue  
        synchronized void enqueue(AsyncCall call) {  
          if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {  
            runningAsyncCalls.add(call);  
            executorService().execute(call);  
          } else {  
            readyAsyncCalls.add(call);  
          }  
        }  
    

    在enqueue的源码中也能够看到dispatch,它的作用:如果当前还能执行一个并发请求,那就立即执行;否则加入readyAsyncCalls队列中,而正在执行的请求执行完毕之后会调用promoteCalls()函数,会将队列中等待的AsyncCall提升为runningAsyncCalls,并开始执行。

    这里的AsyncCall是realCall的一个内部类,它实现了Runnable,所以它可以被提交到ExecutorService上执行,而它的执行时会调用getResponseWithInterceptorChain()函数,并把结果通过responseCallback传递给上层使用者。

    异步同步请求大总结:RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过getResponseWithInterceptorChain() 函数实现。
    而getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络IO等功能。

    四、返回数据的获取:
    在同步或者异步回调请求完成之后,我们就可以从response对象中获取响应数据了,包括HTTP status code , status message , response header , reponse body等。这里body部分最为特殊,因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问,而响应中其他部分则可以随意获取。

    响应body被封装到responseBody类中,该类需要注意的两点:
    1)每个body只能被消费一次,多次消费会抛出异常;
    2)Body必须被关闭,否则会发送资源泄露。

    在从服务器读取响应数据的 CallServerInterceptor中的源码中,我们可以看到body相关代码:

    if (!forWebSocket || response.code() != 101) {  
      response = response.newBuilder()  
          .body(httpCodec.openResponseBody(response))  
          .build();
    

    由 HttpCodec # openResponseBody 提供具体HTTP协议版本的响应body,而 HttpCodec 则是利用Okio实现具体的数据IO操作.

    五、HTTP缓存
    在网络请求的小节中,我们已经看到了Interceptor的布局,在负责和服务器建立连接的ConnectInterceptor之前,就是CacheInterceptor。他们执行过程就是:在建立连接之前,我们检查响应是否已经被缓存、缓存是否可用,如果可用则直接返回缓存的数据,否则就进行后面的流程,并在返回之前把网络的数据写入缓存。
    缓存主要涉及HTTP协议缓存细节的实现,而具体的缓存逻辑OKHttp内置封装了一个Cache;类,它利用DiskLruCache。用磁盘上有限大小的空间进行缓存,按照LRU算法进行缓存淘汰。

    如果觉得有用的话就点个赞吧,哈哈~~~~

    参考博客:https://blog.piasy.com/2016/07/11/Understand-OkHttp/

    相关文章

      网友评论

          本文标题:OKHttp源码详解

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