OkHttp源码解析(一)

大家好,这是本人的第一篇博客,内容如标题所示,关于okhttp。在上个项目中我用的网络框架就是这个。总体效果还是不错的,同时,自己也对OKhttp进行了一个封装,方便自己以后的开发,在这里,就先不把封装的代码放上来了,本片着重于Okhttp的源码。
对于Okhttp的源码,我准备分几篇文章来讲解了,因为Okhttp的源码比较多也比较复杂,有些地方不是很好理解,所以要多花点时间了。希望对大家有用。
我们知道Okhttp可以分为异步请求和同步请求,而对于同步或者异步来说,又有get和post两种方式。今天主要讲的就是同步请求方式。如果想要了解OKhttp的使用方法,可以参考 该篇文章
我们先从okhttp的基本调用开始。
  1. Request request=new Request.Builder().url(url).build();
  2. Call call=mOkHttpClient.newCall(request);
  3. Response response=call.execute();
mOkHttpClient是我实例化的一个OkHttpClient对象。从表面上看过程并不复杂,但是底层逻辑原理是还蛮多的。我们先从httpclient的newCall方法入手,因为在这里返回了一个call对象,而这个对象是最终去执行访问网络的等一系列的操作的。可以看到在newcall方法中接受了一个request参数,这是一个请求体,所有的请求信息都在这里面。我们知道Request request=new Request.Builder().url(url).build()请求体的形式是这样的,所以我们可以进去看看这一系列方法的调用进行了怎样的操作。
        首先是new一个Builder,
  1.  public Builder() {
  2.       this.method = "GET";
  3.       this.headers = new Headers.Builder();
  4.     }
       在这里看到我们默认的请求的方法是get方法。
       这是Request里面的一个内部类。然后调用url方法。看该方法的代码:
  1. public Builder url(String url) {
  2.       if (url == null) throw new IllegalArgumentException("url == null");


  3.       // Silently replace websocket URLs with HTTP URLs.
  4.       if (url.regionMatches(true, 0, "ws:", 0, 3)) {
  5.         url = "http:" + url.substring(3);
  6.       } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
  7.         url = "https:" + url.substring(4);
  8.       }


  9.       HttpUrl parsed = HttpUrl.parse(url);
  10.       if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  11.       return url(parsed);
  12.     }
       在这个方法中,他的主要作用就是检验url的合法性,如果这个url是合法的,那么我们才继续其他参数的添加或者拦截器的拦截等等操作。
    如果我们想要对请求的方法的设置,那么还会调用method(String method, RequestBody body)方法。所以我们再看看该方法:

  1.  public Builder method(String method, RequestBody body) {
  2.       if (method == null || method.length() == 0) {
  3.         throw new IllegalArgumentException("method == null || method.length() == 0");
  4.       }
  5.       if (body != null && !HttpMethod.permitsRequestBody(method)) {
  6.         throw new IllegalArgumentException("method " + method + " must not have a request body.");
  7.       }
  8.       if (body == null && HttpMethod.requiresRequestBody(method)) {
  9.         throw new IllegalArgumentException("method " + method + " must have a request body.");
  10.       }
  11.       this.method = method;
  12.       this.body = body;
  13.       return this;
  14.     }
在这里可以看到是对我们网络请求的方法设置,因为我们之前已经知道我们的默认的方法是GET请求方法。但是有时候我们还想通过post方式去请求,那么在这里就可以进行设置了。
    最后就是builde方法了,

  1. public Request build() {
  2.       if (url == null) throw new IllegalStateException("url == null");
  3.       return new Request(this);
  4.     }
可以看到,在这里仅仅只做了一步操作,也就是返回一个request请求对象。至此,我们的请求参数部分已经结束了。真正的难点部分是网络请求部分,接下来就是网络
请求部分了,我们率先调用的是excute()方法,他的具体代码如下:

  1. public Response execute() throws IOException {
  2.     synchronized (this) {
  3.       if (executed) throw new IllegalStateException("Already Executed");
  4.       executed = true;
  5.     }
  6.     try {
  7.       client.getDispatcher().executed(this);
  8.       Response result = getResponseWithInterceptorChain(false);
  9.       if (result == null) throw new IOException("Canceled");
  10.       return result;
  11.     } finally {
  12.       client.getDispatcher().finished(this);
  13.     }
  14.   }
  在这里我们首先使用了同步锁机制,在这里,他的作用是防止同一个请求重复请求。接着是client.getDispatcher().executed(this);这行代码的作用是对我们的请求进行调度。
在调度的类中我们可以看 到最大请求数是64个,最大同时请求的个数是5个。所以在这里首先要进行一个调配,防止我们的请求数不符合规范。
最重要的方法就是getResponseWithInterceptorChain(false);从这个方法的返回值可以看出,他最终返回的就是我们的网络请求的最终结果了。
  所以进入到这个方法中去:
  1.  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
  2.     Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
  3.     return chain.proceed(originalRequest);
  4.   }
可以看到在这里首先执行的是整个拦截链,所有的拦截操作都将在这里执行,这里的originalRequest就是我们之前封装好的请求。然后调用的是ApplicationInterceptorChain的proceed方法了。
我们进入到该方法中去:

  1. @Override 
  2. public Response proceed(Request request) throws IOException {
  3.       // If there's another interceptor in the chain, call that.
  4.       if (index < client.interceptors().size()) {
  5.         Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
  6.         Interceptor interceptor = client.interceptors().get(index);
  7.         Response interceptedResponse = interceptor.intercept(chain);


  8.         if (interceptedResponse == null) {
  9.           throw new NullPointerException("application interceptor " + interceptor
  10.               + " returned null");
  11.         }


  12.         return interceptedResponse;
  13.       }
  14.        // No more interceptors. Do HTTP.
  15.       return getResponse(request, forWebSocket);
  16.     }
  17.   }
  在我们平时给okhttp添加拦截器的时候调用的方法是这个:mOkHttpClient.interceptors().add(),所以可以看到在这个if中首先判断我们是否设置了拦截器,如果设置了拦截器,
那么我们首先执行拦截器中的内容。而Interceptor是一个接口,所以如果我们想要处理拦截器,那么我们就要去实现这个拦截器了,然后在拦截器中可以去做我们
自己想要做的事了(具体的要实现的方法是intercept(chain))。在这个方法中我们最终会将所有的拦截器执行完毕,这个时候index就会大于client.interceptors().size(),
所以我们就要执行getResponse(request, forWebSocket)方法了。所以不管哪种方式最终都会执行getResponse()方法的。
      这个方法我们会继续去执行我们的请求的操作的,所以进入到该方法中去:
  1. Response getResponse(Request request, boolean forWebSocket) throws IOException {
  2.     // Copy body metadata to the appropriate request headers.
  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.     // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
  21.     engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);


  22.     int followUpCount = 0;
  23.     while (true) {
  24.       if (canceled) {
  25.         engine.releaseStreamAllocation();
  26.         throw new IOException("Canceled");
  27.       }


  28.       boolean releaseConnection = true;
  29.       try {
  30.         engine.sendRequest();
  31.         engine.readResponse();
  32.         releaseConnection = false;
  33.       } catch (RequestException e) {
  34.         // The attempt to interpret the request failed. Give up.
  35.         throw e.getCause();
  36.       } catch (RouteException e) {
  37.         // The attempt to connect via a route failed. The request will not have been sent.
  38.         HttpEngine retryEngine = engine.recover(e);
  39.         if (retryEngine != null) {
  40.           releaseConnection = false;
  41.           engine = retryEngine;
  42.           continue;
  43.         }
  44.         // Give up; recovery is not possible.
  45.         throw e.getLastConnectException();
  46.       } catch (IOException e) {
  47.         // An attempt to communicate with a server failed. The request may have been sent.
  48.         HttpEngine retryEngine = engine.recover(e, null);
  49.         if (retryEngine != null) {
  50.           releaseConnection = false;
  51.           engine = retryEngine;
  52.           continue;
  53.         }


  54.         // Give up; recovery is not possible.
  55.         throw e;
  56.       } finally {
  57.         // We're throwing an unchecked exception. Release any resources.
  58.         if (releaseConnection) {
  59.           StreamAllocation streamAllocation = engine.close();
  60.           streamAllocation.release();
  61.         }
  62.       }


  63.       Response response = engine.getResponse();
  64.       Request followUp = engine.followUpRequest();


  65.       if (followUp == null) {
  66.         if (!forWebSocket) {
  67.           engine.releaseStreamAllocation();
  68.         }
  69.         return response;
  70.       }


  71.       StreamAllocation streamAllocation = engine.close();


  72.       if (++followUpCount > MAX_FOLLOW_UPS) {
  73.         streamAllocation.release();
  74.         throw new ProtocolException("Too many follow-up requests: " + followUpCount);
  75.       }


  76.       if (!engine.sameConnection(followUp.httpUrl())) {
  77.         streamAllocation.release();
  78.         streamAllocation = null;
  79.       }


  80.       request = followUp;
  81.       engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
  82.           response);
  83.     }
  84.   }


这个方法代码比较多,我们只关注重点部分,第一个if中主要的作用就是对请求头进行设置。然后是engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);根据英文的表面意思我们就明白,这里是new出一个http发动机,那么也就是正式的请求在这里开始了。我们发现进入到while的死循环了面了,
所以我猜测所有的请求应该都在这里执行了。首先就是进行判断,判断当前的请求是否取消掉,如果取消掉了,那么就不进行网络请求了。 如果没有取消,那么肯定就开始进行我们的请求操作了。也就是engine.sendRequest();我们进入到其中瞧瞧,
  1. public void sendRequest() throws RequestException, RouteException, IOException {
  2.     if (cacheStrategy != null) return; // Already sent.
  3.     if (httpStream != null) throw new IllegalStateException();


  4.     Request request = networkRequest(userRequest);


  5.     InternalCache responseCache = Internal.instance.internalCache(client);
  6.     Response cacheCandidate = responseCache != null
  7.         ? responseCache.get(request)
  8.         : null;


  9.     long now = System.currentTimeMillis();
  10.     cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  11.     networkRequest = cacheStrategy.networkRequest;
  12.     cacheResponse = cacheStrategy.cacheResponse;


  13.     if (responseCache != null) {
  14.       responseCache.trackResponse(cacheStrategy);
  15.     }


  16.     if (cacheCandidate != null && cacheResponse == null) {
  17.       closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  18.     }


  19.     if (networkRequest != null) {
  20.       httpStream = connect();
  21.       httpStream.setHttpEngine(this);


  22.       // If the caller's control flow writes the request body, we need to create that stream
  23.       // immediately. And that means we need to immediately write the request headers, so we can
  24.       // start streaming the request body. (We may already have a request body if we're retrying a
  25.       // failed POST.)
  26.       if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
  27.         long contentLength = OkHeaders.contentLength(request);
  28.         if (bufferRequestBody) {
  29.           if (contentLength > Integer.MAX_VALUE) {
  30.             throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
  31.                 + "setChunkedStreamingMode() for requests larger than 2 GiB.");
  32.           }


  33.           if (contentLength != -1) {
  34.             // Buffer a request body of a known length.
  35.             httpStream.writeRequestHeaders(networkRequest);
  36.             requestBodyOut = new RetryableSink((int) contentLength);
  37.           } else {
  38.             // Buffer a request body of an unknown length. Don't write request
  39.             // headers until the entire body is ready; otherwise we can't set the
  40.             // Content-Length header correctly.
  41.             requestBodyOut = new RetryableSink();
  42.           }
  43.         } else {
  44.           httpStream.writeRequestHeaders(networkRequest);
  45.           requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
  46.         }
  47.       }


  48.     } else {
  49.       if (cacheResponse != null) {
  50.         // We have a valid cached response. Promote it to the user response immediately.
  51.         this.userResponse = cacheResponse.newBuilder()
  52.             .request(userRequest)
  53.             .priorResponse(stripBody(priorResponse))
  54.             .cacheResponse(stripBody(cacheResponse))
  55.             .build();
  56.       } else {
  57.         // We're forbidden from using the network, and the cache is insufficient.
  58.         this.userResponse = new Response.Builder()
  59.             .request(userRequest)
  60.             .priorResponse(stripBody(priorResponse))
  61.             .protocol(Protocol.HTTP_1_1)
  62.             .code(504)
  63.             .message("Unsatisfiable Request (only-if-cached)")
  64.             .body(EMPTY_BODY)
  65.             .build();
  66.       }


  67.       userResponse = unzip(userResponse);
  68.     }
  69.   }
发现这里的代码也是比较多,我们一行行的看,我们从这一行Request request = networkRequest(userRequest)看起,因为前面两行的作用已经注释了。这行代码的作用就是设置请求头参数。比如在这里设置“Host”,“Connection”等等,其中还有我之前特别需要的cookie(在开发项目中遇到了这个问题,终于在这里找到了)。
  接下来就是网络连接了,首先是判断网络请求是否为“空”,这个空不是代表请求为空,而是因为上面进行了访问策略的判断,如果在本地有缓存,那么就进行网络的访问,直接从缓存中去获取内容,否则就从网络中去获取我们想要的数据,所以有了这行代码,httpStream = connect(),这行代码的作用是获取到一个健康的链接流(connect()方法里面的代码比较多,这里不过多阐述),或者说是可以进行网络访问的连接。然后对请求进行缓存等等一系列处理。在这里有着一个至关重要的一个类,就是Http2xStream,这个类中做了很多事情,而最重要的获取网络数据就是在这个类中获取到的,通过OKio对网络数据的缓存读写。回到getResponse()方法中,现在就是开始执行engine.readResponse();方法了,很明显了。这个方法就是获取到网络返回的response,网络返回的所有数据都封装在这里面了。也就是我们最终得到的response了。
  而在这个方法中有三行代码至关重要,也是很多开发者遇到的问题,
  1. if (userResponse != null) {
  2.       return; // Already ready.
  3.     }
就是这三行代码,这个uerResponse就是我们得到的response,可以看到,这个response我们只能获取到一次,所以我们平时在开的时候我们要将这个response里面的数据缓存起来,否则,第二次获取的时候,我们不会获取到数据的。
总结:从该篇文章我们对okhttp的同步请求方式有了大致的了解,从这些源码来看,我们也可以根据里面的原理对我们平时遇到的问题进行解决。虽然本片文章有些粗略,但是还是很想作为自己的一个笔记记录下来,如果以后遇到这方面的问题,我们也许就可以从这里面找到我们想要的答案。
最后如果本篇文章有什么不对的地方,希望大家给我指正,大家一起进步,谢谢!!








     

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值