okhttp框架 同步请求流程和源码分析

创建Client的外部调用

OKHttpClient client = new OkHttpClient.Builder().readTimeOut(5,TimeUnit.SECONDS).build();

先来看下OkHttpClient的内部类Builder的构造方法:

    public Builder() {
      dispatcher = new Dispatcher(); //okhttp请求的分发器,由它决定异步请求是直接处理,还是进行缓存等待。
//当然,他对同步请求并没有做太多操作,只是把它的同步请求放到队列中去执行。
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();//它其实是一个连接池,我们可以这样理解,客户端和服务端之间的连接,
//我们可以把它抽象为一个connection,
//而每一个connection,我们都会把它放在connectionPool这个连接池当中,他来进行一个统一的管理,
//当你请求到的url是相同的时候,你就可以选择复用,这是作用之一;
//另一个作用就是connectionPool这个类它实现了哪一些网络连接它给保持打开状态,哪一些是可以用来复用的,
//这些策略的设置,也是需要connectionPool这个连接池来进行管理的。
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

这里这么多参数在后面的一些请求流程中都会使用到,这就是Builder创建对象的模式,用Builder对象来封装OkHttp初始化所需要的参数,然后传递我们的Builder对象到OkHttpClient的构造方法里,完成它的整个对象属性的初始化。

当你创建一个对象时,如果这个对象需要很多的参数,这时候,你可以使用到Builder这个创建模式,是一个很好的解决办法。

 

创建Request请求报文信息类

Request的创建方式也是通过Builder来创建的,然后,通过链式调用,给Request指定url,或者头部,以及方法等等。

外部调用如下:

Request request = new Request.Builder().url("http://www.baidu.com").get().build();

先来看下Request的内部类Builder的构造方法。

    public Builder() {
      this.method = "GET";//指定了请求方式为GET,它是默认的
      this.headers = new Headers.Builder();//创建了Headers的Builder对象来保存它的头部信息
    }

接下来看下build()方法,在build()方法里面,他是直接创建一个Request对象,然后把当前的build对象传递过去,它的意思是把之前配置好的请求方式,url,头部,都赋值给Request对象。

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }

还有Request的构造方法。Request构造方法就是为其指定了请求方式,以及请求的网络地址url,以及它的头部信息等等,这样就完成了OkHttp同步请求的前两步。

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.duplex = builder.duplex;
    this.tags = Util.immutableMap(builder.tags);
  }

 

创建Call对象

外部调用:

Call call = client.newCall(request);

它是通过client的newCall来进行实现的。

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   * Call类是一个接口
   * Call类是一个准备执行的请求,可以被取消,代表着一个单一的请求/响应流,不能被执行两次。
   * New一个Call的子类RealCall去执行execute方法
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

由于Call是一个接口,所以它的实际操作都是在它实现类RealCall中所做的,下面来看一下RealCall中是怎么做的。

下面来看下RealCall.java中是怎么做的。

    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
    }

首先,它先创建了它的实现类RealCall对象,然后,还赋值了一个Listener,就返回了。

它究竟做了哪些工作呢?我们到RealCall 的构造方法中看一下。

    private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
        this.timeout = new AsyncTimeout() {
            @Override
            protected void timedOut() {
                cancel();
            }
        };
        this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
    }

RealCall其实是持有了前两步初始化好的okHttpClient,originalRequest,同时,还赋值了一个重定向拦截器。

不管是同步,还是异步请求,它都是调用了client.newCall这个方法来进行创建的,然后我们通过创建好的Call对象进行操作。

 

使用execute完成同步请求

Response response = call.execute();

看看Call.java的execute()方法做了哪些操作。

  /**
   * Invokes the request immediately, and blocks until the response can be processed or is in
   * error.
   *
   * <p>To avoid leaking resources callers should close the {@link Response} which in turn will
   * close the underlying {@link ResponseBody}.
   *
   * <pre>{@code
   *
   *   // ensure the response (and underlying response body) is closed
   *   try (Response response = client.newCall(request).execute()) {
   *     ...
   *   }
   *
   * }</pre>
   *
   * <p>The caller may read the response body with the response's {@link Response#body} method. To
   * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the
   * Response.
   *
   * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
   * not necessarily indicate application-layer success: {@code response} may still indicate an
   * unhappy HTTP response code like 404 or 500.
   *
   * @throws IOException if the request could not be executed due to cancellation, a connectivity
   * problem or timeout. Because networks can fail during an exchange, it is possible that the
   * remote server accepted the request before the failure.
   * @throws IllegalStateException when the call has already been executed.
   */
  Response execute() throws IOException;

execute()是一个接口,再来看下它的具体实现,这是RealCall所做的具体实现。

    /*
    同步方法,不能在UI线程执行
     */
    @Override
    public Response execute() throws IOException {
        synchronized (this) {
            //是否执行过,如果执行过,抛出异常
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        timeout.enter();
        eventListener.callStart(this);
        try {
            //调用Dispatcher的executed方法将该Call加入到一个双端队列里
            client.dispatcher().executed(this);
            //返回本次的Response对象
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } catch (IOException e) {
            e = timeoutExit(e);
            eventListener.callFailed(this, e);
            throw e;
        } finally {
            //从已经执行的双端队列中移除本次Call
            client.dispatcher().finished(this);
        }
    }

在这其中的同步代码块中,它首先判断execute这个标志是否为true,它的意思是同一个http请求只能执行一次,没有执行过,他就会在下面把execute置为true,如果执行过,他就会抛出这个异常。

        synchronized (this) {
            //是否执行过,如果执行过,抛出异常
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }

接下来,他会捕捉一些Http请求的异常堆栈信息。

        captureCallStackTrace();

然后,它会开启了一个监听事件,每当我们的call调用execute方法,或者enqueue方法,就会开启这个Listener。

        eventListener.callStart(this);

调用dispatcher()只是做了一个返回dispatcher对象的操作,dispatcher就是我们请求的分发器。

            //调用Dispatcher的executed方法将该Call加入到一个双端队列里
            client.dispatcher().executed(this);

继续进入这个executed方法。同步请求,它其实通过execute方法把它添加到队列之中,完成了这个操作。

(在okhttp3-master/app/src/main/java/okhttp3/Dispatcher.java)

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

关于Dispatcher,它的作用就是维持Call请求发给它的状态,同时,它也维护了一个线程池,用于执行网络请求,而Call这个请求在执行任务的时候,通过Dispatcher这个分发器类,把它的任务推到执行队列当中,然后来进行操作。

executed()方法主要是将我们的请求加入到同步请求队列当中,这个同步请求队列是在哪里定义的呢?它其实是在Dispatcher.java中定义的。

  //正在运行的同步任务,包含取消尚未完成的调用
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

继续接下来execute()的同步方法,再获取response,这个其实是拦截器链的方法,在这个类内部会一次调用拦截器进行操作。

            //返回本次的Response对象
            Response result = getResponseWithInterceptorChain();

往下走,在finally{}里,它会主动地回收它的某些同步请求。

        finally {
            //从已经执行的双端队列中移除本次Call
            client.dispatcher().finished(this);
        }

进入finished方法看看,在Dispatcher.java里面。

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call);
  }

它又调用了另外一个Finish方法,把正在执行的同步请求传入进来。

  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
      //移除本次call。
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

它首先做的操作就是把队列中移除这个请求,如果不能移除,就抛出异常。

    synchronized (this) {
      //移除本次call。
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

表示Dispatcher分发器中没有可运行的请求了,才可调用run方法。

    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }

关于请求的判断在这个方法里。

  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      //循环判断准备请求队列是否还有请求
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
        //如果执行请求的队列数量大于等于,中止循环
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //小于最大主机请求限制
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
        //从准备队列里移除自身。
        i.remove();
        executableCalls.add(asyncCall);
        //添加到执行队列中。
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
    //对新添加的任务尝试进行异步调用。
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

在同步请求中,dispatcher分发器做的事情非常简单,一是保存同步请求;二是移除同步请求。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章