OkHttp解析大总结

先来看一下Request这个类,此类还是比较简单的,没有太多复杂的代码,内部就是对一些url、method、body等变量的封装。在整个OkHttp的网络请求流程中有一大部分的代码就是用来对这几个变量进行二次修改。源码如下:
这里写图片描述

接着再来看Call这个类,在 OkHttp面试之–OkHttp的整个异步请求流程这一节中我们了解到真正的Call对象应该是RealCall类型,那我们就来看一下这个RealCall
RealCall
从上面的构造器中我们能看出,其内部也保存以一个OkHttpClient和一个Request对象的实例。并且内部还有一个HttpEngine的全局变量,对于HttpEngine目前阶段我们可以将其理解为网络访问的入口—通过HttpEngine进行网络访问操作并返回请求结果.

那在RealCall中是如何使用HttpEngine进行网络访问的呢? 继续分析代码看到在RealCall.enqueue(Callback)方法执行后续流程
这里写图片描述

继续

这里写图片描述

AsyncCall是RealCall中的一个内部类,它就是一个线程我们跟踪其run方法发现其内部最终调用了自身的execute方法,代码如下:
这里写图片描述

用脚后跟想了一下关键的代码肯定是在getResponseWithInterceptorChain这个方法中了吧,继续跟下去吧。。顺便看下这个拦截器”揪净”有几根头发!

这里写图片描述

在getResponseWithInterceptorChain中新建了一个ApplicationInterceptorChain对象,并调用其proceed方法,并且传入的originalRequest就是我们在Activity中所初始化的Request对象。

接下来我们来看看这个ApplicationInterceptorChain是什么。从上图中我们看到它继承了Interceptor.Chain类,点击去看一下:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}

可以看到Interceptor是一个接口,内部只有一个intercept方法,用来处理该拦截器想要如何处理请求。而Chain是Interceptor中的一个内部接口,它里面最重要的方法就是proceed方法。接下来我们回到ApplicationInterceptorChain中看一下它是如何实现proceed方法的

@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);
    }

index默认为0. 而client.interceptors()返回的是OkHttpClient内部的一个全局List变量,如下所示
这里写图片描述
我们可以通过OkHttpClient.addInterceptor(Interceptor对象)的方式向这个集合中添加拦截器对象。一旦集合中有拦截器对象,则会在此方法中进行循环递归的方式遍历client中所有的intercepts(请求前拦截). 在递归遍历的最里层最终调用了最后一行代码getResponse方法获取请求结果,再层层往回传递。

用一张图形象的描述一下过程及就是如下图所示:
这里写图片描述
注意:当我们在自己实现Interceptor时并复写intercept方法时,一定要记住调用chain.proceed方法,否则上面提到的循环递归则会终止,也就是说最终真正的发送网络请求并获取结果的getResponse方法就不会被调用


既然说到拦截器了,我们就来看看OkHttp中的拦截器究竟是个什么玩意。

拦截器是一种强大的机制,可以监视、重写和重试调用。我们可以用拦截器做很多事情,添加我们自己的头部信息,设置有网请求,没网走缓存等
拦截器分为两种拦截器,一种是应用拦截器,一种是网络拦截器。
这两种拦截器的区别主要是以下几点:
网络拦截器和应用拦截器的区别主要有以下几点:

    应用拦截器: 
    1. 不需要担心中间过程的响应,如重定向和重试. 
    2. 总是只调用一次,即使HTTP响应是从缓存中获取. 
    3. 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match. 
    4. 允许短路而不调用 Chain.proceed(),即中止调用. 
    5. 允许重试,使 Chain.proceed()调用多次.

    网络拦截器: 
    1. 能够操作中间过程的响应,如重定向和重试. 
    2. 当网络短路而返回缓存响应时不被调用. 
    3. 只观察在网络上传输的数据. 
    4. 携带请求来访问连接.

至于拦截器的具体使用场景,可以参考Picasso高逼格使用技巧这篇文章中的如下代码

private static OkHttpClient getProgressBarClient(final ProgressListener listener) {  
        return getClient().newBuilder().addNetworkInterceptor(new Interceptor() {  
            @Override  
            public Response intercept(Chain chain) throws IOException {  
                Response originalResponse = chain.proceed(chain.request());  
                return originalResponse.newBuilder()  
                        .body(new ProgressResponseBody(originalResponse.body(), listener))  
                        .build();  
            }  
        }).build();  
    }

好了回到正题上,刚才说了在循环递归遍历的最里层,最终会调用getResponse方法发送网络请求并返回Response对象

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.getLastConnectException(), null);
        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.url())) {
        streamAllocation.release();
        streamAllocation = null;
      }

      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    }
  }

以上代码比较长,我们分段来解释

  • 首先看第3行到21行是根据request中的参数重新构建一个request对象,在Request的头部加入了一些参数
  • 然后在25行根据request和OkHttpClient创建了一个HttpEngine,然后调用此HttpEngine.sendRequest方法发送请求以及readResponse方法读取获取的请求结果信息。
  • 在第44行和54行如果访问时发生了错误,例如RouteException、IOException,则会尝试重连,调用HttpEngine的recover方法,重新生成一个HttpEngine来访问,然后重新进入刚刚的循环
  • 在第78行如果访问成功,并且不需要重定向,则将在第71行通过getResponse方法来获取的请求结果返回
  • 在第72行调用followUpRequest来获取重定向请求,如果此重定向请求不为null,则代码会执行从81行~96行,主要是重新创建一个HttpEngine对象再进行请求操作。这里需要了解下重定向的概念
    • 一个简单的重定向例子就是一个网站,登陆的用户分为两种:管理员和游客,同一个url,对于不同身份的人来说访问到的页面可能不一样,这里就是用了重定向功能,例如该网站的某个页面,如果管理员访问是长某某样子(网页A),游客访问是长某某样子(网页B),这两个网页对应的url也不同,但是初始访问的url都是一样的,这是通过服务器对url和用户解析,返回一个新的url让用户去访问。不知道这么讲大家懂了没。。。一个简单的请求,重定向可以多次,不同的浏览器支持不同的次数。OkHttp框架中重定向最大次数由HttpEngine.MAX_FOLLOW_UPS决定:
      这里写图片描述


上面这段代码基本讲解完毕,但是在代码中,还看到一个概念StreamAllocation,对于StreamAllocation我将专门重新写一篇文章进行讲解–OkHttp中StreamAllocation的分析 这篇文章一定要看!!在了解了什么是StreamAllocation、Http和Socket相关知识之后,我们重新回到刚才的代码中的下列片段,也就是88~95行

if (!engine.sameConnection(followUp.url())) {
        streamAllocation.release();
        streamAllocation = null;
}

      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);

当发现需要重定向的时候,就会执行这段代码,首先先检测重定向的url和刚刚的请求是不是同一个Connection,看下sameConnection函数:

public boolean sameConnection(HttpUrl followUp){
    HttpUrl url = userRequest.url();
    return url.host().equals(followUp.host())
        && url.port() == followUp.port()
        && url.scheme().equals(followUp.scheme());
}

这函数很简单,只是看下这两个url的host、port、scheme是不是一样。如果发现不一样,就释放HttpEngine原来的streamAllocation,并置空,如果发现一样,则重用刚刚的stream。HttpEngine的构造函数里面会判断传入的StreamAllocation是不是为空,若为空则创建一个根据request,并传入ConnectionPool,创建一个streamAllocation,并且从ConnectionPool中取出Connection,并将该Connection记录到StreamAllocation中,如果没有可用的RealConnection,就创建个新的,然后再放到ConnectionPool中


重点开始放到HttpEngine身上

先从followUpRequest()方法下手:
client发送一个request之后,server可能回复一个重定向的response,并在这个response中告知client需要重新访问的server的IP。此时,client需要重新向新的server发送request,并等待新server的回复。所以我们需要单独判断重定向response,并发送多次request。有了OKHttp,这一切你都不用管,它会自动帮你完成所有这一切。OKHttp中followUpRequest()方法就是完成这个功能的。是不是瞬间感觉OKHttp强大到不要不要的啊~。这个方法流程比较简单,就不给出流程图了

public Request followUpRequest() throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Proxy selectedProxy = getRoute() != null
        ? getRoute().getProxy()
        : client.getProxy();
    int responseCode = userResponse.code();

    // 利用responseCode来分析是否需要自动发送后续request
    switch (responseCode) {
      // 未认证用户,不能访问server或代理,故需要发送认证的request
      case HTTP_PROXY_AUTH:
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }

      case HTTP_UNAUTHORIZED:
        return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy);

      // 永久重定向,暂时重定向,永久移动了等和重定向相关的response,response code中以3打头的都是
      // 它们需要重新发送request给新的server,新sever的ip在response中会给出
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        if (!userRequest.method().equals("GET") && !userRequest.method().equals("HEAD")) {
            return null;
        }
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.getFollowRedirects()) return null;

        // 新的server的IP地址在response的Location header中给出
        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userRequest.httpUrl().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userRequest.httpUrl().scheme());
        if (!sameScheme && !client.getFollowSslRedirects()) return null;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userRequest.newBuilder();
        if (HttpMethod.permitsRequestBody(userRequest.method())) {
          requestBuilder.method("GET", null);
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }

        // 删掉用户认证信息
        if (!sameConnection(url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

      default:
        return null;
    }
  }

接下来再看一下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 we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      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();
      return;
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean success = false;
    try {
      httpStream = connect();
      httpStream.setHttpEngine(this);

      if (writeRequestHeadersEagerly()) {
        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);
        }
      }
      success = true;
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (!success && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
  }

代码还是很长的,先来看下第五行,先是调用了一个networkRequest返回了一个request,这个函数就是对原来我们外部传进去的request做了一个封装,封装成一个真正访问网络请求的request。在HttpEngine中有两个request,一个叫userRequest,一个是networkRequest,第一个是外部传入的,未经OkHttp修改的,第二个是根据userRequest封装的一个request,用来访问网络。接下来就是获取InternalCache,Internal.instance是一个单例,该单例初始化是在OkHttpClient里面,可以看到调用Internal.instance.internalCache(client)函数只是调用了client(OkHttpClient)的internalCache,这个函数是返回了我们传入OkHttpClient的cache,如果你创建OkHttpClient的时候,没有传入,那么这里就会返回空。
得到了InternalCache后,尝试根据request去获取response,当然可能为空。接下来就到CacheStrategy了,CacheStrategy是一个决定访问网络还是访问缓存的类。CacheStrategy.Factory是工厂类,通过传入request和刚刚internalCache中获取到的response,去get一个CacheStrategy,Factory.get函数主要是对request和response做了一个检查,会根据response合不合法、是否过期、request的参数来判断,最后返回一个CacheStrategy。CacheStrategy很简单,里面有两个变量,一个是networkRequest,一个是cacheResponse(这个变量和传入Factory的response可能是不一样的哦!)。返回到刚刚的HttpEngine.sendRequest函数中看第17行,如果InternalCache不为空,就调用trackResponse,这个函数很简单,就是记录下缓存读取命中率这些数据。然后如果从InternalCache中response不为空,但是cacheStrategy的response为空,则说明刚刚InternalCache取出来的response无效,则需要关掉。如果CacheStrategy的networkRequest不为空,则说明需要进行网络访问,如果cacheResponse不为空,则说明访问缓存足以。根据这个理论,下面一直到第47行的代码不成问题,就不解释了。接下来看网络访问的部分。
网络访问主要是通过HttpStream来实现,HttpStream这是一个接口,里面定义了一些函数,这些函数是负责读取网络数据、输出数据等。实现该接口的有两个类,一个是Http2xStream,一个是Http1xStream。第一个是专门负责Http2.0版本的,第二个是负责Http1.x版本的,两者内部实现机制不一样。Http2xStream是通过一个FramedConnection,至于对FramedConnection的理解,可以看前面关于Http2.0讲解的文章,看完那个你应该比较能够大概清楚的理解他了,我就不解释了,这里我也没深入去看.
而Http1xStream则是通过sink和source来实现的,至于sink和source是什么将在
OkHttp中sink和source的分析 这一篇中做详细介绍。

今天先写到这里吧,未完待续。。。接下里会跟中Http1XStream中的connect方法实现





参考网址:
http://blog.csdn.net/lintcgirl/article/details/52213570
http://www.infocool.net/kb/OtherMobile/201609/186357.html
http://www.aichengxu.com/view/10953050
http://blog.csdn.net/qq_21430549/article/details/52050553
http://www.codexiu.cn/android/blog/39432/
http://blog.csdn.net/oyangyujun/article/details/50039983
http://blog.csdn.net/oyangyujun/article/details/50040007

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值