大家好,这是本人的第一篇博客,内容如标题所示,关于okhttp。在上个项目中我用的网络框架就是这个。总体效果还是不错的,同时,自己也对OKhttp进行了一个封装,方便自己以后的开发,在这里,就先不把封装的代码放上来了,本片着重于Okhttp的源码。
对于Okhttp的源码,我准备分几篇文章来讲解了,因为Okhttp的源码比较多也比较复杂,有些地方不是很好理解,所以要多花点时间了。希望对大家有用。
我们知道Okhttp可以分为异步请求和同步请求,而对于同步或者异步来说,又有get和post两种方式。今天主要讲的就是同步请求方式。如果想要了解OKhttp的使用方法,可以参考 该篇文章。
我们先从okhttp的基本调用开始。
首先是new一个Builder,
这是Request里面的一个内部类。然后调用url方法。看该方法的代码:
如果我们想要对请求的方法的设置,那么还会调用method(String method, RequestBody body)方法。所以我们再看看该方法:
最后就是builde方法了,
请求部分了,我们率先调用的是excute()方法,他的具体代码如下:
在调度的类中我们可以看 到最大请求数是64个,最大同时请求的个数是5个。所以在这里首先要进行一个调配,防止我们的请求数不符合规范。
最重要的方法就是getResponseWithInterceptorChain(false);从这个方法的返回值可以看出,他最终返回的就是我们的网络请求的最终结果了。
所以进入到这个方法中去:
我们进入到该方法中去:
那么我们首先执行拦截器中的内容。而Interceptor是一个接口,所以如果我们想要处理拦截器,那么我们就要去实现这个拦截器了,然后在拦截器中可以去做我们
自己想要做的事了(具体的要实现的方法是intercept(chain))。在这个方法中我们最终会将所有的拦截器执行完毕,这个时候index就会大于client.interceptors().size(),
所以我们就要执行getResponse(request, forWebSocket)方法了。所以不管哪种方式最终都会执行getResponse()方法的。
这个方法我们会继续去执行我们的请求的操作的,所以进入到该方法中去:
这个方法代码比较多,我们只关注重点部分,第一个if中主要的作用就是对请求头进行设置。然后是engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);根据英文的表面意思我们就明白,这里是new出一个http发动机,那么也就是正式的请求在这里开始了。我们发现进入到while的死循环了面了,
所以我猜测所有的请求应该都在这里执行了。首先就是进行判断,判断当前的请求是否取消掉,如果取消掉了,那么就不进行网络请求了。 如果没有取消,那么肯定就开始进行我们的请求操作了。也就是engine.sendRequest();我们进入到其中瞧瞧,
接下来就是网络连接了,首先是判断网络请求是否为“空”,这个空不是代表请求为空,而是因为上面进行了访问策略的判断,如果在本地有缓存,那么就进行网络的访问,直接从缓存中去获取内容,否则就从网络中去获取我们想要的数据,所以有了这行代码,httpStream = connect(),这行代码的作用是获取到一个健康的链接流(connect()方法里面的代码比较多,这里不过多阐述),或者说是可以进行网络访问的连接。然后对请求进行缓存等等一系列处理。在这里有着一个至关重要的一个类,就是Http2xStream,这个类中做了很多事情,而最重要的获取网络数据就是在这个类中获取到的,通过OKio对网络数据的缓存读写。回到getResponse()方法中,现在就是开始执行engine.readResponse();方法了,很明显了。这个方法就是获取到网络返回的response,网络返回的所有数据都封装在这里面了。也就是我们最终得到的response了。
而在这个方法中有三行代码至关重要,也是很多开发者遇到的问题,
总结:从该篇文章我们对okhttp的同步请求方式有了大致的了解,从这些源码来看,我们也可以根据里面的原理对我们平时遇到的问题进行解决。虽然本片文章有些粗略,但是还是很想作为自己的一个笔记记录下来,如果以后遇到这方面的问题,我们也许就可以从这里面找到我们想要的答案。
最后如果本篇文章有什么不对的地方,希望大家给我指正,大家一起进步,谢谢!!
对于Okhttp的源码,我准备分几篇文章来讲解了,因为Okhttp的源码比较多也比较复杂,有些地方不是很好理解,所以要多花点时间了。希望对大家有用。
我们知道Okhttp可以分为异步请求和同步请求,而对于同步或者异步来说,又有get和post两种方式。今天主要讲的就是同步请求方式。如果想要了解OKhttp的使用方法,可以参考 该篇文章。
我们先从okhttp的基本调用开始。
- Request request=new Request.Builder().url(url).build();
- Call call=mOkHttpClient.newCall(request);
- Response response=call.execute();
首先是new一个Builder,
- public Builder() {
- this.method = "GET";
- this.headers = new Headers.Builder();
- }
这是Request里面的一个内部类。然后调用url方法。看该方法的代码:
- public Builder url(String url) {
- if (url == null) throw new IllegalArgumentException("url == null");
- // Silently replace websocket URLs with HTTP URLs.
- if (url.regionMatches(true, 0, "ws:", 0, 3)) {
- url = "http:" + url.substring(3);
- } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
- url = "https:" + url.substring(4);
- }
- HttpUrl parsed = HttpUrl.parse(url);
- if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
- return url(parsed);
- }
如果我们想要对请求的方法的设置,那么还会调用method(String method, RequestBody body)方法。所以我们再看看该方法:
- public Builder method(String method, RequestBody body) {
- if (method == null || method.length() == 0) {
- throw new IllegalArgumentException("method == null || method.length() == 0");
- }
- if (body != null && !HttpMethod.permitsRequestBody(method)) {
- throw new IllegalArgumentException("method " + method + " must not have a request body.");
- }
- if (body == null && HttpMethod.requiresRequestBody(method)) {
- throw new IllegalArgumentException("method " + method + " must have a request body.");
- }
- this.method = method;
- this.body = body;
- return this;
- }
最后就是builde方法了,
- public Request build() {
- if (url == null) throw new IllegalStateException("url == null");
- return new Request(this);
- }
请求部分了,我们率先调用的是excute()方法,他的具体代码如下:
- public Response execute() throws IOException {
- synchronized (this) {
- if (executed) throw new IllegalStateException("Already Executed");
- executed = true;
- }
- try {
- client.getDispatcher().executed(this);
- Response result = getResponseWithInterceptorChain(false);
- if (result == null) throw new IOException("Canceled");
- return result;
- } finally {
- client.getDispatcher().finished(this);
- }
- }
在调度的类中我们可以看 到最大请求数是64个,最大同时请求的个数是5个。所以在这里首先要进行一个调配,防止我们的请求数不符合规范。
最重要的方法就是getResponseWithInterceptorChain(false);从这个方法的返回值可以看出,他最终返回的就是我们的网络请求的最终结果了。
所以进入到这个方法中去:
- private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
- Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
- return chain.proceed(originalRequest);
- }
我们进入到该方法中去:
- @Override
- public Response proceed(Request request) throws IOException {
- // If there's another interceptor in the chain, call that.
- if (index < client.interceptors().size()) {
- Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
- Interceptor interceptor = client.interceptors().get(index);
- Response interceptedResponse = interceptor.intercept(chain);
- if (interceptedResponse == null) {
- throw new NullPointerException("application interceptor " + interceptor
- + " returned null");
- }
- return interceptedResponse;
- }
- // No more interceptors. Do HTTP.
- return getResponse(request, forWebSocket);
- }
- }
那么我们首先执行拦截器中的内容。而Interceptor是一个接口,所以如果我们想要处理拦截器,那么我们就要去实现这个拦截器了,然后在拦截器中可以去做我们
自己想要做的事了(具体的要实现的方法是intercept(chain))。在这个方法中我们最终会将所有的拦截器执行完毕,这个时候index就会大于client.interceptors().size(),
所以我们就要执行getResponse(request, forWebSocket)方法了。所以不管哪种方式最终都会执行getResponse()方法的。
这个方法我们会继续去执行我们的请求的操作的,所以进入到该方法中去:
- Response getResponse(Request request, boolean forWebSocket) throws IOException {
- // Copy body metadata to the appropriate request headers.
- RequestBody body = request.body();
- if (body != null) {
- Request.Builder requestBuilder = request.newBuilder();
- MediaType contentType = body.contentType();
- if (contentType != null) {
- requestBuilder.header("Content-Type", contentType.toString());
- }
- long contentLength = body.contentLength();
- if (contentLength != -1) {
- requestBuilder.header("Content-Length", Long.toString(contentLength));
- requestBuilder.removeHeader("Transfer-Encoding");
- } else {
- requestBuilder.header("Transfer-Encoding", "chunked");
- requestBuilder.removeHeader("Content-Length");
- }
- request = requestBuilder.build();
- }
- // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
- engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
- int followUpCount = 0;
- while (true) {
- if (canceled) {
- engine.releaseStreamAllocation();
- throw new IOException("Canceled");
- }
- boolean releaseConnection = true;
- try {
- engine.sendRequest();
- engine.readResponse();
- releaseConnection = false;
- } catch (RequestException e) {
- // The attempt to interpret the request failed. Give up.
- throw e.getCause();
- } catch (RouteException e) {
- // The attempt to connect via a route failed. The request will not have been sent.
- HttpEngine retryEngine = engine.recover(e);
- if (retryEngine != null) {
- releaseConnection = false;
- engine = retryEngine;
- continue;
- }
- // Give up; recovery is not possible.
- throw e.getLastConnectException();
- } catch (IOException e) {
- // An attempt to communicate with a server failed. The request may have been sent.
- HttpEngine retryEngine = engine.recover(e, null);
- if (retryEngine != null) {
- releaseConnection = false;
- engine = retryEngine;
- continue;
- }
- // Give up; recovery is not possible.
- throw e;
- } finally {
- // We're throwing an unchecked exception. Release any resources.
- if (releaseConnection) {
- StreamAllocation streamAllocation = engine.close();
- streamAllocation.release();
- }
- }
- Response response = engine.getResponse();
- Request followUp = engine.followUpRequest();
- if (followUp == null) {
- if (!forWebSocket) {
- engine.releaseStreamAllocation();
- }
- return response;
- }
- StreamAllocation streamAllocation = engine.close();
- if (++followUpCount > MAX_FOLLOW_UPS) {
- streamAllocation.release();
- throw new ProtocolException("Too many follow-up requests: " + followUpCount);
- }
- if (!engine.sameConnection(followUp.httpUrl())) {
- streamAllocation.release();
- streamAllocation = null;
- }
- request = followUp;
- engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
- response);
- }
- }
这个方法代码比较多,我们只关注重点部分,第一个if中主要的作用就是对请求头进行设置。然后是engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);根据英文的表面意思我们就明白,这里是new出一个http发动机,那么也就是正式的请求在这里开始了。我们发现进入到while的死循环了面了,
所以我猜测所有的请求应该都在这里执行了。首先就是进行判断,判断当前的请求是否取消掉,如果取消掉了,那么就不进行网络请求了。 如果没有取消,那么肯定就开始进行我们的请求操作了。也就是engine.sendRequest();我们进入到其中瞧瞧,
- public void sendRequest() throws RequestException, RouteException, IOException {
- if (cacheStrategy != null) return; // Already sent.
- if (httpStream != null) throw new IllegalStateException();
- Request request = networkRequest(userRequest);
- InternalCache responseCache = Internal.instance.internalCache(client);
- Response cacheCandidate = responseCache != null
- ? responseCache.get(request)
- : null;
- long now = System.currentTimeMillis();
- cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
- networkRequest = cacheStrategy.networkRequest;
- cacheResponse = cacheStrategy.cacheResponse;
- if (responseCache != null) {
- responseCache.trackResponse(cacheStrategy);
- }
- if (cacheCandidate != null && cacheResponse == null) {
- closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
- }
- if (networkRequest != null) {
- httpStream = connect();
- httpStream.setHttpEngine(this);
- // If the caller's control flow writes the request body, we need to create that stream
- // immediately. And that means we need to immediately write the request headers, so we can
- // start streaming the request body. (We may already have a request body if we're retrying a
- // failed POST.)
- if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
- long contentLength = OkHeaders.contentLength(request);
- if (bufferRequestBody) {
- if (contentLength > Integer.MAX_VALUE) {
- throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
- + "setChunkedStreamingMode() for requests larger than 2 GiB.");
- }
- if (contentLength != -1) {
- // Buffer a request body of a known length.
- httpStream.writeRequestHeaders(networkRequest);
- requestBodyOut = new RetryableSink((int) contentLength);
- } else {
- // Buffer a request body of an unknown length. Don't write request
- // headers until the entire body is ready; otherwise we can't set the
- // Content-Length header correctly.
- requestBodyOut = new RetryableSink();
- }
- } else {
- httpStream.writeRequestHeaders(networkRequest);
- requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
- }
- }
- } else {
- if (cacheResponse != null) {
- // We have a valid cached response. Promote it to the user response immediately.
- this.userResponse = cacheResponse.newBuilder()
- .request(userRequest)
- .priorResponse(stripBody(priorResponse))
- .cacheResponse(stripBody(cacheResponse))
- .build();
- } else {
- // We're forbidden from using the network, and the cache is insufficient.
- this.userResponse = new Response.Builder()
- .request(userRequest)
- .priorResponse(stripBody(priorResponse))
- .protocol(Protocol.HTTP_1_1)
- .code(504)
- .message("Unsatisfiable Request (only-if-cached)")
- .body(EMPTY_BODY)
- .build();
- }
- userResponse = unzip(userResponse);
- }
- }
接下来就是网络连接了,首先是判断网络请求是否为“空”,这个空不是代表请求为空,而是因为上面进行了访问策略的判断,如果在本地有缓存,那么就进行网络的访问,直接从缓存中去获取内容,否则就从网络中去获取我们想要的数据,所以有了这行代码,httpStream = connect(),这行代码的作用是获取到一个健康的链接流(connect()方法里面的代码比较多,这里不过多阐述),或者说是可以进行网络访问的连接。然后对请求进行缓存等等一系列处理。在这里有着一个至关重要的一个类,就是Http2xStream,这个类中做了很多事情,而最重要的获取网络数据就是在这个类中获取到的,通过OKio对网络数据的缓存读写。回到getResponse()方法中,现在就是开始执行engine.readResponse();方法了,很明显了。这个方法就是获取到网络返回的response,网络返回的所有数据都封装在这里面了。也就是我们最终得到的response了。
而在这个方法中有三行代码至关重要,也是很多开发者遇到的问题,
- if (userResponse != null) {
- return; // Already ready.
- }
总结:从该篇文章我们对okhttp的同步请求方式有了大致的了解,从这些源码来看,我们也可以根据里面的原理对我们平时遇到的问题进行解决。虽然本片文章有些粗略,但是还是很想作为自己的一个笔记记录下来,如果以后遇到这方面的问题,我们也许就可以从这里面找到我们想要的答案。
最后如果本篇文章有什么不对的地方,希望大家给我指正,大家一起进步,谢谢!!