OkHttp深入学习

转自:http://blog.csdn.net/evan_man/article/details/51173810


 Android 4.4之后,HttpURLConnection底层实现已被OkHttp替换。可以见得OkHttp的性能已经被Google所认同。对于为何会想深入了解该库的原因:因为它的最底层走到了java的Socket;利用向Socket写入特定的Http协议数据包,实现网络通信。学习该开源项目,对于网络的学历大有益处,除此之外OkHttp使用了缓存和线程池概念。总之个人觉得OkHttp开源项目可以作为学习网络通信一篇很好的教科书

OkHttp的特点:

  • 支持HTTP2/SPDY黑科技
  • socket自动选择最好路线,并支持自动重连
  • 拥有自动维护的socket连接池,减少握手次数
  • 拥有队列线程池,轻松写并发
  • 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  • 实现基于Headers的缓存策略

OkHttp的使用:

  1. 创建OkHttpClient对象:OkHttpClient client = new OkHttpClient();
  2. 创建网络请求:Request request = new Request.Builder()  .url("http://sethfeng.github.io/index.html") .build();
  3. 得到Call对象:Call call = client.newCall(request);  //实际创建的是一个RealCall对象,RealCall中有一个对client对象的引用
  4. 发送同步请求:Response response = call.excute();
  5. 发送异步请求:
    • [java]  view plain  copy
      1. call.enqueue(new Callback() {  
      2.     @Override  
      3.     public void onFailure(Request request, IOException e) {  
      4.         ...  
      5.     }  
      6.     @Override  
      7.     public void onResponse(Response response) throws IOException {  
      8.         ...  
      9.     }  
      10. });  


更多使用方法参考 官方说明文档

OkHttpClient工作原理初步分析

RealCall.class

先来看一下通过 client.newCall(request)得到的Call对象
newCall(request)@OkHttpClient.class
[java]  view plain  copy
  1. Call newCall(Request request) {  
  2.     return new RealCall(this, request);  
  3. }  
方法很简单就是利用调用newCall方法的OkHttpClient对象和newCall的输入参数构造一个RealCall对象。接下来看一下RealCall的构造器。
RealCall()@RealCall.class
[java]  view plain  copy
  1. protected RealCall(OkHttpClient client, Request originalRequest) {  
  2.     this.client = client;  
  3.     this.originalRequest = originalRequest;  
  4.   }  
构造器也很简单,就是对RealCall中的OkHttpClient和Request域进行赋值。 接下来看一下RealCall类中的execute和enqueue方法
execute@RealCall.class 
[java]  view plain  copy
  1. public Response execute() throws IOException      
  2. {  
  3. try {  
  4.             this.client.getDispatcher().executed(this); //同步请求不排队  
  5.              //方法内部会执行synchronized void executed(Call call) {   this.executedCalls.add(call);  }  即把这次请求加入到分发器里  
  6.             Response result = this.getResponseWithInterceptorChain(false);  
  7.             if(result == null) {  
  8.                 throw new IOException("Canceled");  
  9.             }  
  10.             var2 = result;  
  11.  } finally {this.client.getDispatcher().finished(this); }  
  12.     ...  
  13.     return var2;  
  14. }  
该方法完成一个同步请求,首先将这次的同步请求call添加到Dispatcher的工作队列中,随后调用getResponseWithInterceptorChain方法获取request对应的response。最后返回得到的response。
enqueue@RealCall.class 
[java]  view plain  copy
  1. public void enqueue(Callback responseCallback) {  
  2.     enqueue(responseCallback, false);  
  3.  }  
  4. 实际调用下面方法  
  5. void enqueue(Callback responseCallback, boolean forWebSocket) {  
  6.     synchronized (this) {  
  7.       if (executed) throw new IllegalStateException("Already Executed");  
  8.       executed = true;  
  9.     }  
  10.     client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); //异步请求会排队!  
  11. }  
  12. 内部类AsyncCall@RealCall.class  
  13. final class AsyncCall extends NamedRunnable{  
  14. protected void execute() {  
  15.         Response response = getResponseWithInterceptorChain(forWebSocket);  
  16.         if (canceled) {  
  17.           signalledCallback = true;  
  18.           responseCallback.onFailure(RealCall.thisnew IOException("Canceled"));  
  19.         } else {  
  20.           signalledCallback = true;  
  21.           responseCallback.onResponse(RealCall.this, response);  
  22.         }  
  23.        this.client.getDispatcher().finished(this);    
  24.     }  
  25. }  
enqueue则是将请求包装成一个异步请求,异步请求继承自Runnable的子接口,实现了一个execute异步方法,Dispatcher会在合适的时间调用该方法,注意这里的execute方法和前面的execute不是同一个方法。在execute方法内部会调用 getResponseWithInterceptorChain方法获得网络请求的返回值,随后利用回调方法,将结果发送给客户。
Dispatcher负责对客户请求进行处理,因此接下来分析一下Dispatcher的execute和enqueue方法。将在下一节分析getResponseWithInterceptorChain方法的底层实现。

Dispatcher.class

OkHttpClient中有一个Dispatcher类型的域,在构造OkHttpClient的时候会调用new Dispatcher()方法获得一个Dispatcher对象,因此我们直接看Dispatcher的源码。
Dispatcher()@Dispatcher.class
public Dispatcher() {
}
看到这个方法一脸的懵逼。什么也没做?不要方,既然这里没有做任何工作,那么有两种可能,Dispatcher的相关域要么在类加载时或者声明时就进行了初始化,要么就会在使用的时候再临时进行初始化,即延迟初始化,这也是一种优化。那我们直接看execute方法和enqueue方法
executed@Dispatcher.class
[java]  view plain  copy
  1. synchronized void executed(RealCall call) {  
  2.     runningSyncCalls.add(call);   
  3. }  
该方法很简单,就是将Recall请求添加到runningSyncCalls集合中。该集合在构造Dispatcher的时候就初始化好了Deque<RealCall> runningSyncCalls = new ArrayDeque<>();该集合代表着当前正在线程中运行的请求。有的童鞋是不是已经方了?这就完了?我们的请求在哪执行!!??不要忘了我们调用的RealCall的execute方法中,在this.client.getDispatcher().executed(this);语句后面继续调用Response result = this.getResponseWithInterceptorChain(false);方法,而实际的网络请求是在这里进行的。可以说Dispatch主要负责对客户的请求进行管理,留存,备份;重点在于利用线程池对异步请求的处理,同步请求它基本不干活。下面接着看enqueue方法。
enqueue()@Dispatcher.class
[java]  view plain  copy
  1. synchronized void enqueue(AsyncCall call) {  
  2.     if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {  
  3.       // maxRequestsPerHost默认为5 maxRequests默认64  
  4.       runningAsyncCalls.add(call);  
  5.       executorService().execute(call); //交给线程池去执行 call中的execute方法  
  6.     } else {  
  7.       readyAsyncCalls.add(call);    
  8.       //存入等待队列  
  9.       //对于存入这里的请求,在方法promoteCalls()中会被取出,进行执行;  
  10.       //任务执行完成后,调用finished的promoteCalls()函数,不管是异步还是同步请求,它们在执行完execute方法过后都会调用Dispatcher的finished方法  
  11.     }  
  12.   }  
这个方法代码相对于execute就多了一些代码,不过逻辑很简单,首先判断集合runningSyncCalls的大小,即当前运行的请求数是否小于maxRequest;同时判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost。如果条件全为真,则直接将该请求加入到集合runningSyncCalls中,随后调用executorService().execute(call);对异步任务进行处理。否则将异步请求加入到等待异步执行队列readyAsyncCalls中。下面看看executorService方法
executorService()@Dispatcher.class
[java]  view plain  copy
  1. public synchronized ExecutorService executorService() {  
  2.     if (executorService == null) {  
  3.       executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,  
  4.           new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher"false));  
  5.     }  
  6.     return executorService;  
  7. }  
该方法返回了一个线程池,很熟悉吧;类似通过Executors.newCachedThreadPool()方法得到一个线程池。那么调用executorService().execute(call);方法最终会执行异步请求的execute方法,而异步请求的execute方法内部会调用getResponseWithInterceptorChain()方法获得response。
那么到此为止对于execute方法和enqueue方法的介绍就结束了。如果好奇点的童鞋,可能会问,如果异步请求被添加到readyAsyncCalls集合中,那么它何时会被执行呢?注意到,在RealCall的execute和enqueue方法执行完后都会执行this.client.getDispatcher().finished(this);  这样一条语句。那么我们来看下Dispatcher的finished方法。
finished()@Dispatcher.class
[java]  view plain  copy
  1. synchronized void finished(AsyncCall call) {  
  2.     if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");  
  3.     promoteCalls();  
  4. }  
方法很简单,将请求从runningAsyncCalls中移除出去,随后执行 promoteCalls()方法,接着看该方法源码
finished()@Dispatcher.class
[java]  view plain  copy
  1. private void promoteCalls() {  
  2.     if (runningAsyncCalls.size() >= maxRequests) return// Already running max capacity.  
  3.     if (readyAsyncCalls.isEmpty()) return// No ready calls to promote.  
  4.   
  5.     for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {  
  6.       AsyncCall call = i.next();  
  7.   
  8.       if (runningCallsForHost(call) < maxRequestsPerHost) {  
  9.         i.remove();  
  10.         runningAsyncCalls.add(call);  
  11.         executorService().execute(call);  
  12.       }  
  13.   
  14.       if (runningAsyncCalls.size() >= maxRequests) return// Reached max capacity.  
  15.     }  
  16.   }  
在当前运行任务数大于maxRequests和等待执行异步任务数为空的两种情况下直接返回不进行任何操作。否则 从等待执行异步请求集合中获取到请求,判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost,为真则将该任务从等待执行异步请求集合中移出,存入runningAsyncCalls集合中,最后调用线程池执行器执行该异步请求的execute方法。到此为止我们对于Dispatcher的介绍就到此为止了。
对Dispatcher的总结如下:
  • 该类中有两个集合分别为:runningAsyncCalls、readyAsyncCalls前者存放正在执行的请求,后者存放等待执行的请求
  • 该类中有一个newCachedThreadPool线程执行器,利用该执行器来执行异步请求的execute方法。也就是说异步请求发送在非当前工作线程,即创建异步请求的线程,而是从线程池中获取一条线程执行网络请求。同步请求则直接是在当前工作线程中执行。
  • 该类对异步请求的管理是通过maxRequests、maxRequestsPerHost进行控制的,前者控制线程池中同时运行的最大请求数,防止同时运行线程过多,造成OOM。后者限制了同一hostname下的请求数,防止一个应用占用的网络资源过多,优化用户体验。

文章的最后我们对okhttp中使用过程中遇到的Request、Response、OkHttpClient这几个类进行一下介绍。

Request.class

该类中有如下域
[java]  view plain  copy
  1. private final HttpUrl url;  //目标地址  
  2. private final String method; //方法  
  3. private final Headers headers; //请求头,Headers.class里面维护了一个private final String[] namesAndValues;数据集  
  4. private final RequestBody body; //请求表单  
  5. private final Object tag; //标签  
  6. private volatile URI javaNetUri; // Lazily initialized.  
  7. private volatile CacheControl cacheControl; // Lazily initialized.  
只能通过Builder方法构建Request对象。
Builder()@Builder.class@Request.class
[java]  view plain  copy
  1. public Builder() {  
  2.       this.method = "GET";  
  3.       this.headers = new Headers.Builder();  
  4. }  
默认创建的是Get方法
Build()@Builder.class@Request.class
[java]  view plain  copy
  1. public Request build() {  
  2.       if (url == nullthrow new IllegalStateException("url == null");  
  3.       return new Request(this);  
  4. }  
调用Request构造器,创建Request对象。

Response.class

类中有如下域
[java]  view plain  copy
  1. private final Request request;  //对应的request  
  2. private final Protocol protocol; //对应的Http协议  
  3. private final int code; //返回状态码  
  4. private final String message; //Http状态对应的消息  
  5. private final Handshake handshake; //TLS握手协议Transport Layer Security  
  6. private final Headers headers; //返回响应头  
  7. private final ResponseBody body; //Http表单  
  8. private Response networkResponse; //来源于网络的Response,如果响应来自缓存,则该值为null  
  9. private Response cacheResponse; //来自缓存的响应  
  10. private final Response priorResponse;  //在redirect或者授权改变的时候,该结果不为空  
  11. private volatile CacheControl cacheControl; // Lazily initialized.  
Okhttp中有client.internalCache()和client.connectionPool()两个重要的概念,前者管理网络访问的缓存信息,后者用于存储已链接的RealConnection(该RealConnection已经跟对应的hostname完成了三次握手)。下面我们看一下创建Cache和ConnectionPool这两个对象的OkHttpClient对象。

OkHttpClient.class

Internal.instance@OkHttpClient.class
[java]  view plain  copy
  1. static {  
  2.     Internal.instance = new Internal() {  
  3.       @Override public void addLenient(Headers.Builder builder, String line) {  
  4.         builder.addLenient(line);  
  5.       }  
  6.   
  7.       @Override public void addLenient(Headers.Builder builder, String name, String value) {  
  8.         builder.addLenient(name, value);  
  9.       }  
  10.   
  11.       @Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {  
  12.         builder.setInternalCache(internalCache);  
  13.       }  
  14.   
  15.       @Override public InternalCache internalCache(OkHttpClient client) {  
  16.         return client.internalCache();  
  17.       }  
  18.   
  19.       @Override public boolean connectionBecameIdle(  
  20.           ConnectionPool pool, RealConnection connection) {  
  21.         return pool.connectionBecameIdle(connection);  
  22.       }  
  23.   
  24.       @Override public RealConnection get(  
  25.           ConnectionPool pool, Address address, StreamAllocation streamAllocation) {  
  26.         return pool.get(address, streamAllocation);  
  27.       }  
  28.   
  29.       @Override public void put(ConnectionPool pool, RealConnection connection) {  
  30.         pool.put(connection);  
  31.       }  
  32.   
  33.       @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {  
  34.         return connectionPool.routeDatabase;  
  35.       }  
  36.   
  37.       @Override  
  38.       public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {  
  39.         ((RealCall) call).enqueue(responseCallback, forWebSocket);  
  40.       }  
  41.   
  42.       @Override public StreamAllocation callEngineGetStreamAllocation(Call call) {  
  43.         return ((RealCall) call).engine.streamAllocation;  
  44.       }  
  45.   
  46.       @Override  
  47.       public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {  
  48.         tlsConfiguration.apply(sslSocket, isFallback);  
  49.       }  
  50.   
  51.       @Override public HttpUrl getHttpUrlChecked(String url)  
  52.           throws MalformedURLException, UnknownHostException {  
  53.         return HttpUrl.getChecked(url);  
  54.       }  
  55.     };  
  56.   }  
这一段代码用static修饰,表明在加载OkHttpClient类时就会对Internal.instance进行初始化操作。
internalCache() @OkHttpClient.class
[java]  view plain  copy
  1. InternalCache internalCache() {  
  2.     return cache != null ? cache.internalCache : internalCache;  
  3. }  
cache域在我们构造OkHttpClient的时候是没有被初始化的,因此如果我们没有通过调用Builder的cache方法设置cache值的话,该方法返回的对象实际上是一个不支持任何缓存操作的对象,说着说该对象的所有方法为空。因此如果需要OkHttpClient支持缓存,需要我们写一个Cache对象并在构造OkHttpClient的时候将其传给OkHttpClient。
cache()@Builder.class@OkHttpClient.class
[java]  view plain  copy
  1. public Builder cache(Cache cache) {  
  2.       this.cache = cache;  
  3.       this.internalCache = null;  
  4.       return this;  
  5. }  
完成cache的初始化,如果不调用该方法那么OkHttpClient默认不提供Cache功能。对于Cahce更为详细的介绍在后面的章节我们会进行详细介绍。《OkHttp深入学习(三)——Cache》
connectionPool()@OkHttpClient.class
[java]  view plain  copy
  1. public ConnectionPool connectionPool() {  
  2.     return connectionPool;   
  3. }  
connectionPool的初始化是在构建OkHttpClient时创建的,调用的构造器为new ConnectionPool()。

==

RealCall.class

首先直接看getResponseWithInterceptorChain()的源码。
getResponseWithInterceptorChain()@RealCall.class
[java]  view plain  copy
  1. private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {  
  2.     Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket); //是我们传进来的Request  
  3.     return chain.proceed(originalRequest);  
  4. }  
方法真是简单的一逼啊。利用客户请求构造一个ApplicationInterceptorChain对象,随后调用其proceed方法。我们接着往下走
ApplicationInterceptorChain.class@RealCall.class
[java]  view plain  copy
  1. class ApplicationInterceptorChain implements Interceptor.Chain{  
  2.     private final int index;  
  3.     private final Request request;  
  4.     private final boolean forWebSocket;  
  5.     ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {  
  6.       this.index = index;  
  7.       this.request = request;  
  8.       this.forWebSocket = forWebSocket;  
  9.     }  
  10.     public Response proceed(Request request) throws IOException {  
  11.       if (index < client.interceptors().size()) {  
  12.         Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);  
  13.         Interceptor interceptor = client.interceptors().get(index);  
  14.         Response interceptedResponse = interceptor.intercept(chain); //该方法内部会出现chain.proceed语句,即实现递归调用  
  15.         return interceptedResponse; //最终上面的所有  
  16.      }  
  17.      return getResponse(request, forWebSocket);//当递归到最内层时,才会调用到该方法,完成实际的网络请求  
  18.     }  
  19. }  
ApplicationInterceptorChain内部类还算简单,里面有一个index和一个request对象。proceed方法使用了递归,将用户的请求通过拦截器一层一层的包装最后得到一个全新的请求,在递归的最底层即所有的拦截器已经执行完毕后,则调用getResponse方法获取response,通过该请求得到的请求结果最后又利用拦截器一层层的包装最终得到一个全新的网络请求结果。 直接这样说可能不太好理解,下面给出一个普通的拦截器使用例子:
[java]  view plain  copy
  1. class LoggingInterceptor implements Interceptor {  
  2.   @Override public Response intercept(Interceptor.Chain chain) throws IOException {  
  3.     Request request = chain.request();  
  4.     long t1 = System.nanoTime();  
  5.     logger.info(String.format("Sending request %s on %s%n%s",  
  6.         request.url(), chain.connection(), request.headers()));  
  7.     Response response = chain.proceed(request);  
  8.     long t2 = System.nanoTime();  
  9.     logger.info(String.format("Received response for %s in %.1fms%n%s",  
  10.         response.request().url(), (t2 - t1) / 1e6d, response.headers()));  
  11.     return response;  
  12.   }  
  13. }  
这个拦截器很简单就是在请求发送前记录时间,并在结果返回之后记录时间,中间最重要的就是调用了chain.proceed(request)方法和返回一个Response对象,使得递归功能得以实现,否则加了该拦截器,请求将无法继续被执行。因此任何一个正常的迭代器都会出现Response response = chain.proceed(request);  return response;这两行语句的。
ApplicationInterceptorChain拦截器的效果图如下。



chain.proceed方法的最底层是通过调用getResponse方法获得对应的response,接着看看该部分的源码。
getResponse()@RealCall.class 
[java]  view plain  copy
  1. Response getResponse(Request request, boolean forWebSocket) throws IOException {  
  2.     //step1 根据request的body设置request的http报头  
  3.     RequestBody body = request.body();  
  4.     if (body != null) {  
  5.       Request.Builder requestBuilder = request.newBuilder();  
  6.       MediaType contentType = body.contentType();  
  7.       if (contentType != null) {  
  8.         requestBuilder.header("Content-Type", contentType.toString());  
  9.       }  
  10.       long contentLength = body.contentLength();  
  11.       if (contentLength != -1) {  
  12.         requestBuilder.header("Content-Length", Long.toString(contentLength));  
  13.         requestBuilder.removeHeader("Transfer-Encoding");  
  14.       } else {  
  15.         requestBuilder.header("Transfer-Encoding""chunked");  
  16.         requestBuilder.removeHeader("Content-Length");  
  17.       }  
  18.       request = requestBuilder.build();  
  19.     }  
  20.   
  21.     //step2 给每个request创建一个HttpEngine,由该引擎负责网络请求  
  22.     engine = new HttpEngine(client, request, falsefalse, forWebSocket, nullnullnull);  
  23.     int followUpCount = 0;  
  24.     while (true) {  
  25.       if (canceled) {  
  26.         engine.releaseStreamAllocation();  
  27.         throw new IOException("Canceled");  
  28.       }  
  29.   
  30.       boolean releaseConnection = true;  
  31.       try {  
  32.         engine.sendRequest();  
  33.         engine.readResponse();  
  34.         releaseConnection = false;  
  35.       } catch (RequestException e) {  
  36.         // The attempt to interpret the request failed. Give up.  
  37.         throw e.getCause();  
  38.       } catch (RouteException e) {  
  39.         // The attempt to connect via a route failed. The request will not have been sent.  
  40.         HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);  
  41.         if (retryEngine != null) {  
  42.           releaseConnection = false;  
  43.           engine = retryEngine;  
  44.           continue;  
  45.         }  
  46.         // Give up; recovery is not possible.  
  47.         throw e.getLastConnectException();  
  48.       } catch (IOException e) {  
  49.         // An attempt to communicate with a server failed. The request may have been sent.  
  50.         HttpEngine retryEngine = engine.recover(e, null);  
  51.         if (retryEngine != null) {  
  52.           releaseConnection = false;  
  53.           engine = retryEngine;  
  54.           continue;  
  55.         }  
  56.         // Give up; recovery is not possible.  
  57.         throw e;  
  58.       } finally {  
  59.         // We're throwing an unchecked exception. Release any resources.  
  60.         if (releaseConnection) {  
  61.           StreamAllocation streamAllocation = engine.close();  
  62.           streamAllocation.release();  
  63.         }  
  64.       }  
  65.       //正常情况下,前面已经正常执行了engine.sendRequest();  engine.readResponse();   
  66.       Response response = engine.getResponse();  
  67.       if (followUp == null) {  
  68.         if (!forWebSocket) {  
  69.           engine.releaseStreamAllocation();//成功获取到链接时将StreamAllocation和Connection解绑  
  70.         }  
  71.         return response;  
  72.       }  
  73.       //step3 正常情况下上面已经返回response,下面是一些异常情况的处理  
  74.       ........  
  75.     } //end of while(true)  
  76.   }  
该方法首先设置request的head,随后由该request构建一个HttpEngine对象,并依次调用engine.sendRequest()、engine.readResponse()和engine.getResponse()方法。最后将得到的response结果,进行返回。下面我们就依次来分析一下HttpEngine的sendRequest()、readResponse()、getResponse()方法

HttpEngine.class

首先看一下该类的构造器
HttpEngine()@HttpEngine.class
[java]  view plain  copy
  1. public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,  
  2.       boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,  
  3.       RetryableSink requestBodyOut, Response priorResponse) {  
  4.     this.client = client;  
  5.     this.userRequest = request;  
  6.     this.bufferRequestBody = bufferRequestBody;  
  7.     this.callerWritesRequestBody = callerWritesRequestBody;  
  8.     this.forWebSocket = forWebSocket;  
  9.     this.streamAllocation = streamAllocation != null  
  10.         ? streamAllocation  
  11.         : new StreamAllocation(client.connectionPool(), createAddress(client, request));  
  12.     this.requestBodyOut = requestBodyOut;  
  13.     this.priorResponse = priorResponse;  
  14.   }  
这里我们主要关注的是传入的OkHttpClient client, Request request两个参数,这两个参数在HttpEngine中的命名分别为client和userRequest。 此外还注意到构造器中对this.streamAllocation = streamAllocation != null   ? streamAllocation   : new StreamAllocation(client.connectionPool(), createAddress(client, request));的初始化。默认都是通过调用new StreamAllocation(client.connectionPool(), createAddress(client, request))获得StreamAllocation对象的。对StreamAllocation它主要负责对RealConnection的管理,我们先不看其源码,后面再讲。先看sendRequest()方法。
sendRequest()@HttpEngine.class
[java]  view plain  copy
  1. public void sendRequest() throws RequestException, RouteException, IOException {  
  2.     ....  
  3.     Request request = networkRequest(userRequest); //note1  
  4.     InternalCache responseCache = Internal.instance.internalCache(client); //note2  
  5.     Response cacheCandidate = responseCache != null? responseCache.get(request) : null;  
  6.   
  7.     long now = System.currentTimeMillis();  
  8.     cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(); //note3  
  9.     networkRequest = cacheStrategy.networkRequest;  
  10.     cacheResponse = cacheStrategy.cacheResponse;  
  11.     if (responseCache != null) { //OkHttpClient具备缓存功能  
  12.       responseCache.trackResponse(cacheStrategy);  
  13.     }  
  14.     if (cacheCandidate != null && cacheResponse == null) { //在OkHttpClient缓存中得到response当时经过缓存策略分析该response无效  
  15.       closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.  
  16.     }  
  17.     // 网络请求为空,且本地命中的缓存也为空,则返回错误的504http报文  
  18.     if (networkRequest == null && cacheResponse == null) {  
  19.       userResponse = new Response.Builder()  
  20.           .request(userRequest)  
  21.           .priorResponse(stripBody(priorResponse))  
  22.           .protocol(Protocol.HTTP_1_1)  
  23.           .code(504)  
  24.           .message("Unsatisfiable Request (only-if-cached)")  
  25.           .body(EMPTY_BODY)  
  26.           .build();  
  27.       return;  
  28.     }  
  29.     //网络请求为空,本地命中的缓存也不为空,返回本地命中的response  
  30.     if (networkRequest == null) {  
  31.       userResponse = cacheResponse.newBuilder()  
  32.           .request(userRequest)  
  33.           .priorResponse(stripBody(priorResponse))  
  34.           .cacheResponse(stripBody(cacheResponse))  
  35.           .build();  
  36.       userResponse = unzip(userResponse);  
  37.       return;  
  38.     }  
  39.   
  40.     //note4  
  41.     boolean success = false;  
  42.     try {  
  43.       httpStream = connect();  
  44.       httpStream.setHttpEngine(this);  
  45.   
  46.       if (writeRequestHeadersEagerly()) {  
  47.         long contentLength = OkHeaders.contentLength(request);  
  48.         if (bufferRequestBody) {  
  49.           if (contentLength > Integer.MAX_VALUE) {  
  50.             throw new IllegalStateException("Use setFixedLengthStreamingMode() or "  
  51.                 + "setChunkedStreamingMode() for requests larger than 2 GiB.");  
  52.           }  
  53.   
  54.           if (contentLength != -1) {  
  55.             // Buffer a request body of a known length.  
  56.             httpStream.writeRequestHeaders(networkRequest);  
  57.             requestBodyOut = new RetryableSink((int) contentLength);  
  58.           } else {  
  59.             // Buffer a request body of an unknown length. Don't write request headers until the  
  60.             // entire body is ready; otherwise we can't set the Content-Length header correctly.  
  61.             requestBodyOut = new RetryableSink();  
  62.           }  
  63.         } else {  
  64.           httpStream.writeRequestHeaders(networkRequest);  
  65.           requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);  
  66.         }  
  67.       }  
  68.       success = true;  
  69.     } finally {  
  70.       // If we're crashing on I/O or otherwise, don't leak the cache body.  
  71.       if (!success && cacheCandidate != null) {  
  72.         closeQuietly(cacheCandidate.body());  
  73.       }  
  74.     }  
  75.   }  
1、该方法很长,我们慢慢消化。networkRequest(userRequest);方法功能上是对request中的http报头进行相应修改,如cookies,使其符合Http数据报的协议规范。
2、对于InternalCache responseCache = Internal.instance.internalCache(client),一脸的懵逼,之前从来没有见过,不过不要方,它在我们构建OkHttpClient的时候被初始化的!让我们回头撸一把OkHttpClient,果然发现有下面的一段内容, Internal.instance = new Internal() { .....}。这下我们就知道Internal.instance对象在构造OkHttpClient的时候就创建好了。实际上Internal.instance.internalCache(client)等价于 client.internalCache();即得到一个缓存操作符。随后调用缓存操作符的get方法得到对应的缓存结果。
3、cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();利用一定的缓存策略对request和cacheCandidate进行处理,并对HttpEngine中的networkRequest和cacheResponse对象进行赋值。如果得到的networkRequest==null,表明缓存策略认为该请求不需要访问网络,如果cacheResponse==null表明该请求在缓存中没有找到想要的结果。可能有童鞋不太懂为何要这样做,其实举个栗子就懂了,就是比如我们本地有缓存内容,但是可能该内容存放时间太长已经过期了,系统不能再使用该缓存,需要重新去网上下载。如果对该部分比较感兴趣参考下一篇博客 《OkHttp深入学习(三)——Cache》,本节不对其进行详细说明。
4、经过上面的分析如果没有从缓存中得到预期的结果,那么需要通过网络得到预期的response。首先执行的是 httpStream = connect();  该方法 内部调用streamAllocation.newStream()方法;骚年你还记得吗?streamAllocation是在构造HttpEngine时创建的对象。调用的构造器为new StreamAllocation(client.connectionPool(), createAddress(client, request)); 对于OkHttpClient的链接池connectionPool部分参考后面的OkHttpClient.class部分的分析。构造完StreamAllocation之后调用其newStream方法。得到一个HttpStream对象,该对象是一个已经跟服务器端进行了三次握手的链接,通过该对象就能向服务器发送接收Http报文数据。对于connect()后面的内容,一般情况我们不太会遇到,下面直接开始分析readResponse()方法
readResponse()@HttpEngine.class
[java]  view plain  copy
  1. public void readResponse() throws IOException {  
  2.     if (userResponse != null) {  
  3.       return// 已经通过缓存获取到了响应  
  4.     }  
  5.     if (networkRequest == null && cacheResponse == null) {  
  6.       throw new IllegalStateException("call sendRequest() first!");  
  7.     }  
  8.     if (networkRequest == null) {  
  9.       return// No network response to read.  
  10.     }  
  11.     //note1  
  12.     Response networkResponse;  
  13.     if (forWebSocket) {  
  14.       httpStream.writeRequestHeaders(networkRequest);  
  15.       networkResponse = readNetworkResponse();  
  16.     } else if (!callerWritesRequestBody) {  
  17.       networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);  
  18.     } else {  
  19.       // Emit the request body's buffer so that everything is in requestBodyOut.  
  20.       if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {  
  21.         bufferedRequestBody.emit();  
  22.       }  
  23.   
  24.       // Emit the request headers if we haven't yet. We might have just learned the Content-Length.  
  25.       if (sentRequestMillis == -1) {  
  26.         if (OkHeaders.contentLength(networkRequest) == -1  
  27.             && requestBodyOut instanceof RetryableSink) {  
  28.           long contentLength = ((RetryableSink) requestBodyOut).contentLength();  
  29.           networkRequest = networkRequest.newBuilder()  
  30.               .header("Content-Length", Long.toString(contentLength))  
  31.               .build();  
  32.         }  
  33.         httpStream.writeRequestHeaders(networkRequest);  
  34.       }  
  35.   
  36.       // Write the request body to the socket.  
  37.       if (requestBodyOut != null) {  
  38.         if (bufferedRequestBody != null) {  
  39.           // This also closes the wrapped requestBodyOut.  
  40.           bufferedRequestBody.close();  
  41.         } else {  
  42.           requestBodyOut.close();  
  43.         }  
  44.         if (requestBodyOut instanceof RetryableSink) {  
  45.           httpStream.writeRequestBody((RetryableSink) requestBodyOut);  
  46.         }  
  47.       }  
  48.   
  49.       networkResponse = readNetworkResponse();  
  50.     }  
  51.     //note 2  
  52.     receiveHeaders(networkResponse.headers());  
  53.     //note 3  
  54.     // If we have a cache response too, then we're doing a conditional get.  
  55.     if (cacheResponse != null) {  
  56.       if (validate(cacheResponse, networkResponse)) {  
  57.         userResponse = cacheResponse.newBuilder()  
  58.             .request(userRequest)  
  59.             .priorResponse(stripBody(priorResponse))  
  60.             .headers(combine(cacheResponse.headers(), networkResponse.headers()))  
  61.             .cacheResponse(stripBody(cacheResponse))  
  62.             .networkResponse(stripBody(networkResponse))  
  63.             .build();  
  64.         networkResponse.body().close();  
  65.         releaseStreamAllocation();//调用streamAllocation.release();方法,作用就是将StreamAllocation和内部的connection脱离关系  
  66.   
  67.         // Update the cache after combining headers but before stripping the  
  68.         // Content-Encoding header (as performed by initContentStream()).  
  69.         InternalCache responseCache = Internal.instance.internalCache(client);  
  70.         responseCache.trackConditionalCacheHit();  
  71.         responseCache.update(cacheResponse, stripBody(userResponse));  
  72.         userResponse = unzip(userResponse);  
  73.         return;  
  74.       } else {  
  75.         closeQuietly(cacheResponse.body());  
  76.       }  
  77.     }  
  78.     //note 4  
  79.     userResponse = networkResponse.newBuilder()  
  80.         .request(userRequest)  
  81.         .priorResponse(stripBody(priorResponse))  
  82.         .cacheResponse(stripBody(cacheResponse))  
  83.         .networkResponse(stripBody(networkResponse))  
  84.         .build();  
  85.     //note 5  
  86.     if (hasBody(userResponse)) {  
  87.       maybeCache();  
  88.       userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));  
  89.     }  
  90.   }  
1、对于一般情况我们会执行networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest)方法,与ApplicationInterceptorChain的功能类似,这里是在向网络服务器发送数据前嵌套几层拦截器,采用递归方法实现。递归方法的最底层会调用Response response = readNetworkResponse();方法得到网络返回数据,之后又通过拦截器进行相应处理。不过无论通过哪条路径最终都是通过调用readNetworkResponse()方法获得Response的。这里给出NetWorkInterceptorChain的工作原理图

2、将得到的networkResponse中的cookies存入OkHttpClient中的cookieJar对象中,该对象默认是空的,即不能存入任何url的cookie,如果我们在构造OkHttpClient的时候给予它一个CookieJar那么OkHttpClient就会将每次获得的cookie都存入我们定义的CookieJar中。一般是把url作为key,cookies作为value。
3、如果该Request在OkHttpC中的缓存中存在对应的Response,则更新该缓存
4、利用获得的networkResponse给userResponse赋值
5、根据得到的Response判断是否存入缓存
因为上面得到的Response来自于方法readNetworkResponse(),那么接下来我们来分析一下该方法是如何工作的。

readNetworkResponse()@HttpEngine.class
[java]  view plain  copy
  1. private Response readNetworkResponse() throws IOException {  
  2.     httpStream.finishRequest();  
  3.     Response networkResponse = httpStream.readResponseHeaders()  
  4.         .request(networkRequest)  
  5.         .handshake(streamAllocation.connection().handshake())  
  6.         .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))  
  7.         .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))  
  8.         .build();  
  9.     if (!forWebSocket) {  
  10.       networkResponse = networkResponse.newBuilder()  
  11.           .body(httpStream.openResponseBody(networkResponse))  
  12.           .build();  
  13.     }  
  14.     if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))  
  15.         || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {  
  16.       streamAllocation.noNewStreams();  
  17.     }  
  18.     return networkResponse;  
  19.   }  
该方法逻辑很简单就是调用httpStream.readResponseHeaders()方法和httpStream.openResponseBody方法获取到Response。那么我们继续看HttpStream的readResponseHeaders方法和httpStream.openResponseBody方法。走到这里突然你可能忘了HttpStream从哪里得到的。这里我们复习一下。在构造HttpEngine时创建的对象的时候我们创建了一个streamAllocation对象。调用的构造器为new StreamAllocation(client.connectionPool(), createAddress(client, request)); 对于OkHttpClient的链接池connectionPool部分参考后面的OkHttpClient.class部分的分析。构造完StreamAllocation之后调用其newStream方法,得到一个HttpStream对象。我们先看一下getResponse方法,随后即可进入StreamAllocation和其newStream方法学习。
getResponse()@HttpEngine.class
[java]  view plain  copy
  1. public Response getResponse() {  
  2.     if (userResponse == nullthrow new IllegalStateException();  
  3.     return userResponse;  
  4. }  
该方法很简单就是返回HttpEngine中的userResponse域。

StreamAllocation.class

StreamAllocation@StreamAllocation.class
[java]  view plain  copy
  1. public StreamAllocation(ConnectionPool connectionPool, Address address) {  
  2.     this.connectionPool = connectionPool;  
  3.     this.address = address;  
  4.     this.routeSelector = new RouteSelector(address, routeDatabase());  
  5.   }  
这里额外的创建了一个RouteSelector对象,该对象由Address和路由数据库构造。
routeDatabase()@StreamAllocation.class
[java]  view plain  copy
  1. private RouteDatabase routeDatabase() {  
  2.     return Internal.instance.routeDatabase(connectionPool);  
  3. }  
该方法其实返回的就是connectionPool.routeDatabase; 该routeDatabase实际上用于存储之前访问失败的路由代理。上面看完了StreamAllocation的构造器,赶紧来看看其newStream方法。
newStream()@StreamAllocation.class
[java]  view plain  copy
  1. public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,  
  2.       boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)  
  3.       throws RouteException, IOException {  
  4.     try {  
  5.       RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, //note1  
  6.           writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);  
  7.   
  8.       HttpStream resultStream; //note2  
  9.       if (resultConnection.framedConnection != null) {  
  10.         resultStream = new Http2xStream(this, resultConnection.framedConnection); //在这里支持HTTP/2 and SPDY黑科技  
  11.       } else {  
  12.         resultConnection.socket().setSoTimeout(readTimeout);  
  13.         resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);  
  14.         resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);  
  15.         resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink); //支持1.1协议;  
  16.       }  
  17.   
  18.       synchronized (connectionPool) {  
  19.         stream = resultStream;  
  20.         return resultStream;  
  21.       }  
  22.     } catch (IOException e) {  
  23.       throw new RouteException(e);  
  24.     }  
  25.   }  
1、首先利用findHealthyConnection()方法得到一个RealConnection对象。 得到了RealConnection对象意味着我们已经跟服务端有了一条通信链路了,而且通过RealConnection的source 和  sink分别实现向管道中读写数据。
2、根据请求的协议不同创建不同的HttpStream,一般情况下我们创建的都是Http1xStream对象,即Http1.X协议。 该对象功能就是对request中的数据按照对应的http协议中的格式暴力的通过sink写入到管道流中,对response中的数据通过source读取并进行解析。
该部分的重点转移到了方法findHealthyConnection方法,往下看。
findHealthyConnection()@StreamAllocation.class
[java]  view plain  copy
  1. while (true) {  
  2.       RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,  
  3.           connectionRetryEnabled);  
  4.       // If this is a brand new connection, we can skip the extensive health checks.  
  5.       synchronized (connectionPool) {  
  6.         if (candidate.successCount == 0) {  
  7.           return candidate;  
  8.         }  
  9.       }  
  10.       // Otherwise do a potentially-slow check to confirm that the pooled connection is still good.  
  11.       if (candidate.isHealthy(doExtensiveHealthChecks)) {  
  12.         return candidate;  
  13.       }  
  14.       connectionFailed(new IOException());  
  15. }  
该方法的大体流程就是先通过findConnection获取到一个可用的链接,随后对该链接进行一定的健康性检查,如果不通过则继续调用findConnection寻找直到找到为止。继续看findConnection方法
findConnection()@StreamAllocation.class
[java]  view plain  copy
  1. private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,  
  2.       boolean connectionRetryEnabled) throws IOException, RouteException {  
  3.     Route selectedRoute;  
  4.     synchronized (connectionPool) {  
  5.       if (released) throw new IllegalStateException("released");  
  6.       if (stream != nullthrow new IllegalStateException("stream != null");  
  7.       if (canceled) throw new IOException("Canceled");  
  8.       //note 1  
  9.       RealConnection allocatedConnection = this.connection;  
  10.       if (allocatedConnection != null && !allocatedConnection.noNewStreams) {  
  11.         return allocatedConnection;  
  12.       }  
  13.   
  14.       //note 2  
  15.       RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);  
  16.       if (pooledConnection != null) {  
  17.         this.connection = pooledConnection;  
  18.         return pooledConnection;  
  19.       }  
  20.       selectedRoute = route;   
  21.       //如果是第一次走到这里 则route为null  
  22.     }  
  23.   
  24.     if (selectedRoute == null) {  
  25.       selectedRoute = routeSelector.next();  
  26.        //note 3  
  27.       synchronized (connectionPool) {  
  28.         route = selectedRoute;  
  29.       }  
  30.     }  
  31.     RealConnection newConnection = new RealConnection(selectedRoute);   
  32.      //note 4  
  33.     acquire(newConnection);   
  34.     //note 5  
  35.     synchronized (connectionPool) {  
  36.       Internal.instance.put(connectionPool, newConnection);    
  37.       //note 6  
  38.       this.connection = newConnection;  
  39.       if (canceled) throw new IOException("Canceled");  
  40.     }  
  41.   
  42.     newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(), connectionRetryEnabled);   
  43.      //note7  
  44.     routeDatabase().connected(newConnection.route());     
  45.     //note 8  
  46.     return newConnection;  
  47.   }  
1、如果当前StreamAllocation对象中的RealConnection不为空,且没有新的Stream则直接返回该RealConnection。
2、从线程池中获取一个RealConnection对象,等价于直接调用connectionPool.get(address, streamAllocation),如果获取到了RealConnection则直接返回该RealConnection。step1和step2得到的RealConnection都是已经跟服务器完成了三次握手链接的连接。
3、利用routeSelector.next()方法得到一个route对象;routeSelector在构造StreamAllocation时创建的跟我们的request是绑定的;每个链接都需要选择一个代理,IP地址,TLS。
4、通过得到的route对象构建一个RealConnection对象,该route包含了客户请求链接的ip地址和端口号等信息。
5、connection.allocations.add(new WeakReference<>(this)); 为当前StreamAllocation创建一个弱引用,添加到RealConnection的allocation中,即增加其引用计数。
6、将上面创建的RealConnection添加到OkHttpClient的ConnectionPool链接池中。
7、调用RealConnection的connect方法, 创建链接, 完成跟目标地址的的三次握手。
8、将newConnection对应的route从routeDatabase中移除出去,routeDatabase实际上维护一个集合,里面存入的route都是failed

最后给大家科普StreamAllocation最后一个方法,该方法用于回收StreamAllocation对应的Connection。调用StreamAllocation的noNewStreams和release方法都会调用到该方法。 一般在任务执行结束后都会通过HttpEngine的releaseStreamAllocation()方法间接调用StreamAllocation的deallocate方法将RealConnection和StreamAllocation进行解绑。
deallocate@S treamAllocation.class
[java]  view plain  copy
  1. private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {  
  2.     RealConnection connectionToClose = null;  
  3.     synchronized (connectionPool) {  
  4.       if (streamFinished) {  
  5.         this.stream = null;  
  6.       }  
  7.       if (released) {  
  8.         this.released = true;  
  9.       }  
  10.       if (connection != null) {  
  11.         if (noNewStreams) {  
  12.           connection.noNewStreams = true//noNewStreams表明该链接不能提供给新Stream使用  
  13.         }  
  14.         if (this.stream == null && (this.released || connection.noNewStreams)) {  
  15.           release(connection); //note1  
  16.           if (connection.allocations.isEmpty()) { //note2  
  17.             connection.idleAtNanos = System.nanoTime();  
  18.             if (Internal.instance.connectionBecameIdle(connectionPool, connection)) { //note3  
  19.               connectionToClose = connection;  
  20.             }  
  21.           }  
  22.           connection = null;  
  23.         }  
  24.       }  
  25.     }  
  26.     if (connectionToClose != null) { //note4  
  27.       Util.closeQuietly(connectionToClose.socket());  
  28.     }  
  29.   }  
1、完成的工作就是把当前的StreamAllocation从Realconnection的allocations队列中移除出去。如果没有在RealConnection的Allocation集合中找到对StreamAllocation的引用则抛出异常。
2、Connection的Allocation队列如果为空,表明该RealConnection没有被任何StreamAllocation使用。
3、Internal.instance.connectionBecameIdle(connectionPool, connection)等价于pool.connectionBecameIdle(connection); 该方法内部判断connection.noNewStreams为真,即该Connection不能给新的stream提供服务,则将该Connection从Connectionpool中移除出去,同时返回true。否则让Connectionpool的清理线程去处理,返回false。
4、第三步中返回结果为真,即RealConnection已经从ConnectionPool中移除,则在此处强制调用socket的close方法,关闭套接字,回收网络资源。

继续往下走,那就是RealConnection对象的创建和其connected方法的实现。RealConnection这也是我们所能走到的最底层的类了。胜利的曙光要来了~


RealConnection.class


private Socket rawSocket; //最底层socket
public Socket socket; //应用层socket
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
RealConnection@RealConnection.class
[java]  view plain  copy
  1. public RealConnection(Route route) {  
  2.     this.route = route;  
  3. }  
connect@RealConnection.class
[java]  view plain  copy
  1. public void connect(int connectTimeout, int readTimeout, int writeTimeout,List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {  
  2.     if (protocol != nullthrow new IllegalStateException("already connected");  
  3.     RouteException routeException = null;  
  4.     ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);  
  5.     Proxy proxy = route.proxy();  
  6.     Address address = route.address();  
  7.     if (route.address().sslSocketFactory() == null  
  8.         && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {  
  9.       throw new RouteException(new UnknownServiceException(  
  10.           "CLEARTEXT communication not supported: " + connectionSpecs));  
  11.     }  
  12.     while (protocol == null) {  
  13.       try {  
  14.         //note1  
  15.         rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP  
  16.             ? address.socketFactory().createSocket() //对于ssl的情况  
  17.             : new Socket(proxy);  
  18.         //note 2 上面调用的方法已经很底层了!!使用的是jdk中的内容了  
  19.         connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);  
  20.       } catch (IOException e) {  
  21.         ......  
  22.       }  
  23.     }  
  24.   }  
1、利用proxy的type值判断构造一个怎样的Socket,是普通Socket还是SSL性质的Socket。
2、调用 connectSocket方法。
 connectSocket()@RealConnection.class
[java]  view plain  copy
  1. private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout, ConnectionSpecSelector connectionSpecSelector) throws IOException {   
  2.     rawSocket.setSoTimeout(readTimeout);  
  3.     try {  
  4.       //note 1  
  5.       Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);  
  6.     } catch (ConnectException e) {  
  7.       throw new ConnectException("Failed to connect to " + route.socketAddress());  
  8.     }  
  9.     //note 2  
  10.     source = Okio.buffer(Okio.source(rawSocket));  
  11.     sink = Okio.buffer(Okio.sink(rawSocket));  
  12.     if (route.address().sslSocketFactory() != null) {  
  13.       connectTls(readTimeout, writeTimeout, connectionSpecSelector);  
  14.     } else {  
  15.       protocol = Protocol.HTTP_1_1;  
  16.       socket = rawSocket;  
  17.     }  
  18.     if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {  
  19.       socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.  
  20.       FramedConnection framedConnection = new FramedConnection.Builder(true)  
  21.           .socket(socket, route.address().url().host(), source, sink)  
  22.           .protocol(protocol)  
  23.           .listener(this)  
  24.           .build();  
  25.       framedConnection.sendConnectionPreface();  
  26.       // Only assign the framed connection once the preface has been sent successfully.  
  27.       this.allocationLimit = framedConnection.maxConcurrentStreams();  
  28.       this.framedConnection = framedConnection;  
  29.     } else {  
  30.       this.allocationLimit = 1;  
  31.     }  
  32.   }  
这部分是最激动人心的地方,这里我们就要开始跟外界进行通信了~,也是我们这次对okhttp项目学习的最底层了。
1、Platform.get()等价于Platform.findPlatform();结果是根据平台的不同得到不同的Platform对象,使用Class.forName方法获取对应的类对象;下面假设平台是Android平台。Platform. get().  connectSocket() 方法等价于调用socket.connect(address, connectTimeout); 完成了TCP三次握手
2、source = Okio.buffer(Okio.source(rawSocket));就是从rawSocket获得一个InputStream再用buffer包装一下。 sink = Okio.buffer(Okio.sink(rawSocket));   就是从rawSocket获得一个OutputStream再用buffer包装一下。 到此为止我们已经获得了跟服务器通信的链路。

在本节结束之前我们最后对ConnectionPool和RouteSelector进行介绍

ConnectionPool.class

[java]  view plain  copy
  1. private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,  
  2.       Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,  
  3.       new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool"true));  
  4. //该类跟Dispatcher.class一样创建了一个线程池,用于维护管理RealConnection  
  5. final RouteDatabase routeDatabase = new RouteDatabase();  
  6. //该对象创建一个RouteDataBase对象,是一个路由数据库,该数据库其实很简单,里面维护了一个private final Set<Route> failedRoutes = new LinkedHashSet<>()集合。存放失败的路由数据  
  7. private final Deque<RealConnection> connections = new ArrayDeque<>();  
  8. //该集合是ConnectionPool用于存储当前系统经历过三次握手可用的RealConnection  
ConnectionPool()@ConnectionPool.class
[java]  view plain  copy
  1. public ConnectionPool() {  
  2.     this(55, TimeUnit.MINUTES);  
  3. }  
  4. public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {  
  5.     this.maxIdleConnections = maxIdleConnections;  //最大空闲连接数  
  6.     this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);  
  7.     // Put a floor on the keep alive duration, otherwise cleanup will spin loop.  
  8.     if (keepAliveDuration <= 0) {  
  9.       throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);  
  10.     }  
  11.   }  
创建的连接池默认维护5条自由链接,且自由链接空闲时间最多为5分钟。RealConnection是否是自由态由RealConnection中存储的Allocations集合的大小决定。如果RealConnection.noNewStream==true则表明该RealConnection拒绝为新StreamAllocation服务,往往意味着等待被回收。
get()@ConnectionPool.class
[java]  view plain  copy
  1. RealConnection get(Address address, StreamAllocation streamAllocation) {  
  2.     assert (Thread.holdsLock(this));  
  3.     for (RealConnection connection : connections) {  
  4.       if (connection.allocations.size() < connection.allocationLimit //note1  
  5.           && address.equals(connection.route().address) //note2  
  6.           && !connection.noNewStreams) { //note3  
  7.         streamAllocation.acquire(connection); //note4  
  8.         return connection;  
  9.       }  
  10.     }  
  11.     return null;  
  12.   }  
该方法的作用就是尝试从集合connections中获取到一个可重复利用的RealConnection。
1、connection.allocations.size() < connection.allocationLimit;判断该RealConnection所服务的StreamAllocation数量是否小于门限值。
2、address.equals(connection.route().address);该Connection的hostname地址等于方法参数的address值。
3、connection.noNewStreams能否被其它StreamAllocation。
4、streamAllocation.acquire(connection);等价于 connection.allocations.add(streamAllocation) ,将StreamAllocation添加到RealConnection的allocations集合中。增加RealConnection的引用计数。当该引用计数为0时考虑回收该RealConnection。

put()@ConnectionPool.class
[java]  view plain  copy
  1. void put(RealConnection connection) {  
  2.     assert (Thread.holdsLock(this));  
  3.     if (!cleanupRunning) {  
  4.       cleanupRunning = true;  
  5.       executor.execute(cleanupRunnable);//note1  
  6.     }  
  7.     connections.add(connection);//note2  
  8.   }  
1、对connections集合进行维护,cleanupRunnable的run方法内部会执行cleanup方法,下面我们将对其进行介绍
2、将RealConnection加入到ConnectionPool的connects集合
cleanup()@ConnectionPool.class
[java]  view plain  copy
  1. long cleanup(long now) {  
  2.     int inUseConnectionCount = 0;  
  3.     int idleConnectionCount = 0;  
  4.     RealConnection longestIdleConnection = null;  
  5.     long longestIdleDurationNs = Long.MIN_VALUE;  
  6.     // Find either a connection to evict, or the time that the next eviction is due.  
  7.     synchronized (this) {  
  8.       for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {  
  9.         RealConnection connection = i.next();  
  10.         // note1  
  11.         if (pruneAndGetAllocationCount(connection, now) > 0) {  
  12.           inUseConnectionCount++;  
  13.           continue;  
  14.         }  
  15.         idleConnectionCount++;  
  16.         // If the connection is ready to be evicted, we're done.  
  17.         long idleDurationNs = now - connection.idleAtNanos; //note 2  
  18.         if (idleDurationNs > longestIdleDurationNs) {  
  19.           longestIdleDurationNs = idleDurationNs;  
  20.           longestIdleConnection = connection;  
  21.         }  
  22.       } // end of for  
  23.       if (longestIdleDurationNs >= this.keepAliveDurationNs  
  24.           || idleConnectionCount > this.maxIdleConnections) { //note3  
  25.         connections.remove(longestIdleConnection);  
  26.       } else if (idleConnectionCount > 0) {  
  27.         //返回下次正常情况下次需要检查的等待时间  
  28.         return keepAliveDurationNs - longestIdleDurationNs;  
  29.       } else if (inUseConnectionCount > 0) {  
  30.         //返回下次正常情况下次需要检查的等待时间  
  31.         return keepAliveDurationNs;  
  32.       } else {  
  33.         //池中没有自由链接,也没有正在使用的链接  
  34.         cleanupRunning = false;  
  35.         return -1;  
  36.       }  
  37.     }  
  38.     closeQuietly(longestIdleConnection.socket()); //note4  
  39.     // Cleanup again immediately.  
  40.     return 0;  
  41.   }  
Connectionpool维护其链接池中链接;该方法是在cleanupRunnable中的run方法中被调用。
1、缩减和获得与该Connection绑定的StreamAllocation数量,如果数量不为0,证明该RealConnection正在被某个StreamAllocation使用,否则进行下面的步骤。
2、获取该Connection的自由时间,如果该链接自由时间超过当前系统所记录的Connection最长自由时间,则刷新当前记录最大值。这是标记过程
3、执行到这里,已经得到了当前系统空闲线程等待的最长时间,如果该时间大于系统设定的最大自由时间或自由链接数大于系统所能维护的最大自由链接数,则将该RealConnection从链接池中移除出去。
4、执行到这里,表明刚刚有一个链接从连接池中被移出,此处将关闭该RealConnection对应的socket,即执行socket.close().
connectionBecameIdle@ConnectionPool.class
[java]  view plain  copy
  1. boolean connectionBecameIdle(RealConnection connection) {  
  2.     assert (Thread.holdsLock(this));  
  3.     if (connection.noNewStreams || maxIdleConnections == 0) {  
  4.       connections.remove(connection);  
  5.       return true;  
  6.     } else {  
  7.       notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.  
  8.       return false;  
  9.     }  
  10.   }  
该方法在StreamAllocation的deallocate中被调用。用于将connection回收,或者将RealConnection变成自由态。

我们对ConnectionPool做个小结:
    Connection pool 创建了一个线程池,用于维护池中的自由链接数,RealConnection采用引用计数的方法判断一个Connection是否是自由态,如果RealConnection的Allocations集合为空则判断为自由态。最后采用标记清除的算法实现对废弃RealConnection的垃圾回收。当自由态链接数大于门限或者链接空闲时间超过门限值时则对该RealConnection资源进行回收,具体工作就是将RealConnection从ConnectionPool的connections集合中移出,底层调用socket.close()关闭网络连接。

RouteSelector.class

   在StreamAllocation的findConnection方法中在构造RealConnection之前是需要获得一个route对象的,而route对象的获取是通过调用routeSelector的next方法来获取的。该route对象包含了我的url请求对应的ip地址和对应端口号。
[java]  view plain  copy
  1. /* State for negotiating the next proxy to use. */  
  2. private List<Proxy> proxies = Collections.emptyList();  
  3. private int nextProxyIndex;  
  4. /* State for negotiating the next socket address to use. */  
  5. private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();  
  6. private int nextInetSocketAddressIndex;  
  7. private final List<Route> postponedRoutes = new ArrayList<>();  
RouteSelector()@ RouteSelector.class
[java]  view plain  copy
  1. public RouteSelector(Address address, RouteDatabase routeDatabase) {  
  2.     this.address = address;  
  3.     this.routeDatabase = routeDatabase;  
  4.     resetNextProxy(address.url(), address.proxy());  
  5. }  
该构造其中的Address参数是在HttpEngine构建StreamAllocation时创建的,创建方法是通过调用HttpEngine的createAddress(client, request)方法来获得的,方法参数分别为OkHttpClient和Request。在RouteSelector的构造器中会调用resetNextProxy方法,参数为客户请求的url和对应的代理,一般情况刚开始时代理是空。
resetNextProxy()@ RouteSelector.class
[java]  view plain  copy
  1. private void resetNextProxy(HttpUrl url, Proxy proxy) {  
  2.     if (proxy != null) {  
  3.       //note1  
  4.       proxies = Collections.singletonList(proxy);  
  5.     } else {  
  6.       proxies = new ArrayList<>();  
  7.       List<Proxy> selectedProxies = address.proxySelector().select(url.uri()); //note2  
  8.       if (selectedProxies != null) proxies.addAll(selectedProxies);  
  9.       // Finally try a direct connection. We only try it once!  
  10.       proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));  
  11.       proxies.add(Proxy.NO_PROXY);  
  12.     }  
  13.     nextProxyIndex = 0;  
  14. }  
1、如果代理不为空,则直接对proxies赋值
2、address.proxySelector()等价于client.proxySelector(),后者默认等价于ProxySelector.getDefault()等价于new java,net.ProxySelectorImpl();经历这么一堆等价于其实简单讲就是调用address.proxySelector()等于创建一个java,net.ProxySelectorImpl()对象。随后调用该对象的select方法获取与该url对应的Proxy
下面我们就来看看next方法具体完成的操作。
next()@ RouteSelector.class
[java]  view plain  copy
  1. public Route next() throws IOException {  
  2.     if (!hasNextInetSocketAddress()) {  
  3.       if (!hasNextProxy()) {  
  4.         if (!hasNextPostponed()) {  
  5.           //前面三个队列都为空  
  6.           throw new NoSuchElementException();  
  7.         }//end if 3  
  8.         return nextPostponed();//返回最后一个之前失败的route  
  9.       }//end if 2  
  10.       lastProxy = nextProxy();//note1  
  11.     }//end if 1  
  12.     lastInetSocketAddress = nextInetSocketAddress(); //note 2  
  13.     Route route = new Route(address, lastProxy, lastInetSocketAddress);  //note 3  
  14.     if (routeDatabase.shouldPostpone(route)) {  //note 4  
  15.       postponedRoutes.add(route); //note 5  
  16.       return next(); //note 6  
  17.     }  
  18.     return route; //返回可以用的route  
  19.   }  
1、首先运行到这里,获取下一个代理,同时刷新集合inetSocketAddresses的数据
2、获取到InetSocketAddress
3、利用前面得到的代理和SocketAddress构造一个Route
4、查找route是否存在于routeDatabase中,即检验生成的route是不是可用
5、step4返回真,该route加入到postponedRoutes集合中,如果最后所有的代理都试过了还是不行,则还会将该route重新再尝试一次
6、递归调用
nextProxy()@ RouteSelector.class
[java]  view plain  copy
  1. private Proxy nextProxy() throws IOException {  
  2.     if (!hasNextProxy()) {  
  3.       throw new SocketException("No route to " + address.url().host()  
  4.           + "; exhausted proxy configurations: " + proxies);  
  5.     }  
  6.     Proxy result = proxies.get(nextProxyIndex++);//note1  
  7.     resetNextInetSocketAddress(result);  
  8.     return result;  
  9. }  
从Proxies对象中获取一个Proxy对象。proxies的值是在resetNextProxy()方法中获得的
resetNextInetSocketAddress()@ RouteSelector.class
[java]  view plain  copy
  1. private void resetNextInetSocketAddress(Proxy proxy) throws IOException {  
  2.     // Clear the addresses. Necessary if getAllByName() below throws!  
  3.     inetSocketAddresses = new ArrayList<>();  
  4.     String socketHost;  
  5.     int socketPort;  
  6.     if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {  
  7.       socketHost = address.url().host();  
  8.       socketPort = address.url().port();  
  9.     } else {  
  10.       SocketAddress proxyAddress = proxy.address();  
  11.       if (!(proxyAddress instanceof InetSocketAddress)) {  
  12.         throw new IllegalArgumentException(  
  13.             "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());  
  14.       }  
  15.       InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;  
  16.       socketHost = getHostString(proxySocketAddress);  
  17.       socketPort = proxySocketAddress.getPort();  
  18.     }  
  19.     //获取得到Address对应的IP和Port  
  20.     if (socketPort < 1 || socketPort > 65535) {  
  21.       throw new SocketException("No route to " + socketHost + ":" + socketPort  
  22.           + "; port is out of range");  
  23.     }  
  24.     if (proxy.type() == Proxy.Type.SOCKS) {  
  25.       inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));  
  26.     } else {  
  27.       // Try each address for best behavior in mixed IPv4/IPv6 environments.  
  28.       List<InetAddress> addresses = address.dns().lookup(socketHost);  //DNS查询的结果 url对应多个ip地址和端口号  
  29.       for (int i = 0, size = addresses.size(); i < size; i++) {  
  30.         InetAddress inetAddress = addresses.get(i);  
  31.         inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));  
  32.       }  
  33.     }  
  34.     nextInetSocketAddressIndex = 0;  
  35. }  
根据参数Proxy的值,得到一个java.net.InetSocketAddress类型对象,该对象包含了请求url对应的ip和端口信息。


至此我们对于okhttp的网络通信功能的实现进行了了解,下面对本节进行一下总结:
1、该部分的层次关系有这样的形式:httpEngine->httpStream->RealConnection。
2、无论是同步还是异步的请求最终都是通过getResponseWithInterceptorChain()方法来获得对应的Response。在真实的网络请求之前会使用ApplicationInterceptorChain拦截器对请求进行拦截。之后利用request和OkHttpClient创建一个HttpEngine对象,该对象持有有一个对StreamAllocation对象的引用,通过调用该对象的engine.sendRequest()方法发送请求,通过调用engine.readResponse();方法读取响应数据,同时给HttpEngine中的userResponse域赋值,最后通过engine.getResponse()返回HttpEngine中的userResponse域。
3、 engine.sendRequest()方法, 在通过HttpEngine的sendRequest方法发送请求的到网络上时,会 先去OkHttpClient的Cache中 查找是否有对应的Response数据,如果有则直接对userResponse进行赋值并返回,如果没有则通过StreamAllocation的newStream方法获得的HttpStream对象,随后利用该对象访问网络的响应。StreamAllocation的newstream方法首先 从OKHttpClient中的链接池connection s中尝试获取到RealConnection对象,如果没有获得则利用Route对象创建一个RealConnection,该route是由OkHttpClient和request共同构建得到的,里面有请求主机的对应IP和端口等信息,随后调用该对象的connect方法,操作一个socket完成跟指定主机三次握手。随后将该connection加入到OkHttpClient的链接池中。将上面得到的RealConnection根据不同的Http协议创建不同的HttpStream对象,该对象的作用是根据不同的Http协议,将用户的请求转换成特定的格式后通过RealConnection写入到对应ip的服务端中,然后对通过RealConnection读取到的数据进行解析。
4、engine.readResponse()方法,该方法首先检测usrResponse是否为空,即是否通过上面的sendRequest方法已经获得了缓存Response,有则直接返回usrResponse,如果没有获得则 一般情况下会通过networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest)获得Response。该部分与ApplicationInterceptorChain的功能类似,只是拦截的位置出现变化,这些拦截器更加靠近网络发送时的位置,越底层。所有拦截器执行结束后,通过调用 readNetworkResponse()方法得到网络返回数据,该方法内部通过HttpStream向服务端写入和读取数据。随后对返回数据的cookies进行存储,并判断是否加入缓存中,最后将结果赋值给userResponse。

===



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值