okhttp3.2源码分析(一)

作者: 李冬冬 | 来源:发表于2016-04-15 18:10 被阅读579次

    okhttp简单使用

    1. 创建Request
    2. 创建Call,将Request添加到Call中
    3. 使用异步enqueue,或者同步的execute方法获得结果

    okhttp网络请求过程分析

    Call

    同步请求

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

    首先加锁置标志位,接着使用分配器的executed方法将call加入到同步队列中,然后调用getResponseWithInterceptorChain方法(稍后分析)执行http请求,最后调用finishied方法将call从同步队列中删除

    异步请求

    void enqueue(Callback responseCallback, boolean forWebSocket) {
      synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
      }
      client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
    }
    

    同样先置标志位,然后将封装的一个执行体放到异步执行队列中。这里面引入了一个新的类AsyncCall,这个类继承于NamedRunnable,实现了Runnable接口。NamedRunnable可以给当前的线程设置名字,并且用模板方法将线程的执行体放到了execute方法中,所以我们分析AsyncCall只需要看execute方法

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
    

    通过getResponseWithInterceptorChain方法来执行http请求,这个方法是不是很熟悉,在同步请求中也是用的这个方法来执行http请求。紧接着判断call是否被cancel来执行不同的回调,最后使用finished方法将call从异步执行队列中移除。这里有个需要注意的地方,onResponse回调被执行的条件是本次http请求是完整的,也就是说即使服务器返回的是错误信息,依然会走onResponse回调,我们在应用层使用的时候,可以自己再封装一次。

    OK,以上就是okhttp可以同时支持同步和异步请求的分析过程,而在getResponseWithInterceptorChain方法中我们将会分析okhttp的另一个重要模块:拦截器

    拦截器

    这是我最喜欢okhttp的地方,你可以拦截当前正在发出的请求。我们可以使用拦截器做很多事情,例如:添加log方便调试,在服务器还没有ready的情况下模拟一个网络应答等。在getResponseWithInterceptorChain方法中处理了拦截器的相关逻辑

    private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
      Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
      return chain.proceed(originalRequest);
    }
    

    这里ApplicationInterceptorChain实现了Interceptor.Chain接口,然后在preceed方法中处理相应的逻辑,preceed代码如下

    @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在getResponseWithInterceptorChain方法中被初始化为0,当我们添加了拦截器之后index < client.interceptors().size()就会走到true的代码段,之后会从client.interceptors()中拿出一个拦截器,执行我们的拦截回调。这里也可以看到在拦截回调中是必须要有个Response返回的,否则会出现异常。如果没有自定义拦截器的话,将会调用getResponse方法执行真正的网络请求逻辑(相对于拦截器模块来说是执行了真正的网络请求,其实后面还有缓存模块)

    有意思的是我们可以定义多个拦截器,这就对应了ApplicationInterceptorChain类的名称应用拦截链。只要我们在自定义的拦截器回调方法中调用chan.proceed,拦截器就会链式的调用下去。如果我们不希望okhttp执行真正的网络请求,只需要在拦截器中虚拟一个response即可。需要注意的是,如果某个拦截器内部没有调用chan.proceed方法,那么在它之后添加的拦截器都不会再被执行

    getResponse方法将会把网络请求交给Engine处理

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

    getResponse会先根据request body的contentType来设置相应的header,Content-Length相信都比较熟悉,在http header中Transfer-Encoding: chunked表示的是内容长度不定,这里比较奇怪的是header的属性分别在不同的设置,不清楚为何不放在一起设置。接着会创建一个HttpEngine对象,设置追加发送的请求次数,在HttpEngine中处理网络请求代码如下

    engine.sendRequest();
    engine.readResponse();
    Response response = engine.getResponse();
    

    紧接着是处理各种异常和发送追加请求,获取发送追加请求是在HttpEnginefollowUpRequest方法中处理,在三种情况下okhttp会发送追加请求,通过MAX_FOLLOW_UPS = 20控制最大追加请求

    1. 未授权(401):调用okhttpclient授权方法重新授权
    2. 重定向(3xx)
    3. 请求超时(408):重复发送原请求

    未完待续...

    相关文章

      网友评论

        本文标题:okhttp3.2源码分析(一)

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