OkHttp全解析

闲来无事阅读了一下OkHttp的源码,发现OkHttp根本就不基于HttpURLConnection或者HttpClient

OkHttp是基于java Socket自己实现了一套java层http协议,当然还支持SPDY,https,http2.0等。

用OkHttp,一个请求怎么去写呢

OkHttpClient client = new OkHttpClient();
       Request request =new Request.Builder().url(apiRequest.requestUrl).post(apiRequest.requestParams.getRequestBody()).build();
        try {
            Response response = client.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }

或者

        Request request = new Request.Builder().url(url).post(requestBody).build();
        client.newCall(request).enqueue(callback);

简单来说呢,就是传入RequestBody和url就可以发起请求,同步和异步两种方式。

先看OkHttpClient的构造,首先是静态初始化

  static {
    Internal.instance = new Internal() {
      @Override public Transport newTransport(
          Connection connection, HttpEngine httpEngine) throws IOException {
        return connection.newTransport(httpEngine);
      }

      @Override public boolean clearOwner(Connection connection) {
        return connection.clearOwner();
      }

      @Override public void closeIfOwnedBy(Connection connection, Object owner) throws IOException {
        connection.closeIfOwnedBy(owner);
      }

      @Override public int recycleCount(Connection connection) {
        return connection.recycleCount();
      }

      @Override public void setProtocol(Connection connection, Protocol protocol) {
        connection.setProtocol(protocol);
      }

      @Override public void setOwner(Connection connection, HttpEngine httpEngine) {
        connection.setOwner(httpEngine);
      }

      @Override public boolean isReadable(Connection pooled) {
        return pooled.isReadable();
      }

      @Override public void addLenient(Headers.Builder builder, String line) {
        builder.addLenient(line);
      }

      @Override public void addLenient(Headers.Builder builder, String name, String value) {
        builder.addLenient(name, value);
      }

      @Override public void setCache(OkHttpClient client, InternalCache internalCache) {
        client.setInternalCache(internalCache);
      }

      @Override public InternalCache internalCache(OkHttpClient client) {
        return client.internalCache();
      }

      @Override public void recycle(ConnectionPool pool, Connection connection) {
        pool.recycle(connection);
      }

      @Override public RouteDatabase routeDatabase(OkHttpClient client) {
        return client.routeDatabase();
      }

      @Override public Network network(OkHttpClient client) {
        return client.network;
      }

      @Override public void setNetwork(OkHttpClient client, Network network) {
        client.network = network;
      }

      @Override public void connectAndSetOwner(OkHttpClient client, Connection connection,
          HttpEngine owner, Request request) throws RouteException {
        connection.connectAndSetOwner(client, owner, request);
      }

      @Override
      public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
        call.enqueue(responseCallback, forWebSocket);
      }

      @Override public void callEngineReleaseConnection(Call call) throws IOException {
        call.engine.releaseConnection();
      }

      @Override public Connection callEngineGetConnection(Call call) {
        return call.engine.getConnection();
      }

      @Override public BufferedSource connectionRawSource(Connection connection) {
        return connection.rawSource();
      }

      @Override public BufferedSink connectionRawSink(Connection connection) {
        return connection.rawSink();
      }

      @Override public void connectionSetOwner(Connection connection, Object owner) {
        connection.setOwner(owner);
      }

      @Override
      public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
        tlsConfiguration.apply(sslSocket, isFallback);
      }
    };
  }

其实就是实现了一个Internal类并初始化一个实例,全局通用。

而真正的构造函数里就两行

  public OkHttpClient() {
    routeDatabase = new RouteDatabase();
    dispatcher = new Dispatcher();
  }

没有什么好解释的,后面会着重去看Dispatcher

然后我们构造Request对象,用client.newCall.execute或者enqueue,去执行。

  protected Call(OkHttpClient client, Request originalRequest) {
    // Copy the client. Otherwise changes (socket factory, redirect policy,
    // etc.) may incorrectly be reflected in the request when it is executed.
    this.client = client.copyWithDefaults();
    this.originalRequest = originalRequest;
  }

newCall将OkHttpClient和request对象传进来,当然这里的copy复制了相关配置并且做了一些额外初始化。

先看execute方法做了什么

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

在Dispatcher中,executed方法将Call加入executedCalls队列。这个队列用于处理同步请求,同时readyCalls和runningCalls两个队列用于处理异步请求。

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.okhttp;

import com.squareup.okhttp.Call.AsyncCall;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.HttpEngine;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Policy on when async requests are executed.
 *
 * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you
 * supply your own executor, it should be able to run {@linkplain #getMaxRequests the
 * configured maximum} number of calls concurrently.
 */
public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;

  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  /** Ready calls in the order they'll be run. */
  private final Deque<AsyncCall> readyCalls = new ArrayDeque<>();

  /** Running calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningCalls = new ArrayDeque<>();

  /** In-flight synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<Call> executedCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  public synchronized ExecutorService getExecutorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

  /**
   * Set the maximum number of requests to execute concurrently. Above this
   * requests queue in memory, waiting for the running calls to complete.
   *
   * <p>If more than {@code maxRequests} requests are in flight when this is
   * invoked, those requests will remain in flight.
   */
  public synchronized void setMaxRequests(int maxRequests) {
    if (maxRequests < 1) {
      throw new IllegalArgumentException("max < 1: " + maxRequests);
    }
    this.maxRequests = maxRequests;
    promoteCalls();
  }

  public synchronized int getMaxRequests() {
    return maxRequests;
  }

  /**
   * Set the maximum number of requests for each host to execute concurrently.
   * This limits requests by the URL's host name. Note that concurrent requests
   * to a single IP address may still exceed this limit: multiple hostnames may
   * share an IP address or be routed through the same HTTP proxy.
   *
   * <p>If more than {@code maxRequestsPerHost} requests are in flight when this
   * is invoked, those requests will remain in flight.
   */
  public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
    if (maxRequestsPerHost < 1) {
      throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
    }
    this.maxRequestsPerHost = maxRequestsPerHost;
    promoteCalls();
  }

  public synchronized int getMaxRequestsPerHost() {
    return maxRequestsPerHost;
  }

  synchronized void enqueue(AsyncCall call) {
    if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningCalls.add(call);
      getExecutorService().execute(call);
    } else {
      readyCalls.add(call);
    }
  }

  /** Cancel all calls with the tag {@code tag}. */
  public synchronized void cancel(Object tag) {
    for (AsyncCall call : readyCalls) {
      if (Util.equal(tag, call.tag())) {
        call.cancel();
      }
    }

    for (AsyncCall call : runningCalls) {
      if (Util.equal(tag, call.tag())) {
        call.get().canceled = true;
        HttpEngine engine = call.get().engine;
        if (engine != null) engine.disconnect();
      }
    }

    for (Call call : executedCalls) {
      if (Util.equal(tag, call.tag())) {
        call.cancel();
      }
    }
  }

  /** Used by {@code AsyncCall#run} to signal completion. */
  synchronized void finished(AsyncCall call) {
    if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
  }

  private void promoteCalls() {
    if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningCalls.add(call);
        getExecutorService().execute(call);
      }

      if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

  /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningCalls) {
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

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

  /** Used by {@code Call#execute} to signal completion. */
  synchronized void finished(Call call) {
    if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  }

  public synchronized int getRunningCallCount() {
    return runningCalls.size();
  }

  public synchronized int getQueuedCallCount() {
    return readyCalls.size();
  }
}

getResponseWithInterceptorChain这个方法去做真正的请求,请求完毕后调用client.getDispatcher().finished(),将这个Call从同步请求队列中移出。

先不管真正的请求在哪里去做,现在来看异步的请求怎么做。
在enqueue方法中(看上面dispacher代码),如果runningCalls队列的数量没有超出范围,就加入线程池执行,否则加入readyCalls队列。

任务的执行线程池单独贴出来

  public synchronized ExecutorService getExecutorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

第一个参数,表示线程池中默认策略不保留线程,超过了等待时间就回收,第二个参数表示最大线程数量,第三四个参数表示等待60秒后线程被回收,第四个参数表示存储未被执行Runnable对象的队列,这里用SynchronousQueue,意思是如果这个queue里有一个Runnable对象,入队操作就会被阻塞,同样如果没有Runnable对象,出队操作就会被阻塞。在这个参数设置下,可以认为只要有新的Runnable加入,就立即分配线程去执行,如果没有空闲线程就创建一个并执行。

好了现在执行是执行了,那请问怎么回调回来呢。

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

在enqueue的时候,把Call包装成了一个AsyncCall,而线程池执行的也是这个AsyncCall,我们看一下它的实现

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

原来也是在执行的时候调用了getResponseWithInterceptorChain,拿到结果了之后回调(子线程)。至于为什么它是一个Runnable以及为什么执行的时候调的是execute很简单就不说了,最后同样调用了client.getDispatcher().finished(this)

  /** Used by {@code AsyncCall#run} to signal completion. */
  synchronized void finished(AsyncCall call) {
    if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
  }

  private void promoteCalls() {
    if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningCalls.add(call);
        getExecutorService().execute(call);
      }

      if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

这个函数干了两件事,第一件事是把执行完的AsyncCall从RunningCalls中出队,第二件事是尝试将ReadyCalls队列中的请求加入Running中并执行。

好了现在解释了同步和异步两种请求是怎么实现的,接下来看真正的请求在哪里做。

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

  class ApplicationInterceptorChain implements Interceptor.Chain {
    private final int index;
    private final Request request;
    private final boolean forWebSocket;

    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
      this.index = index;
      this.request = request;
      this.forWebSocket = forWebSocket;
    }

    @Override public Connection connection() {
      return null;
    }

    @Override public Request request() {
      return request;
    }

    @Override public Response proceed(Request request) throws IOException {
      if (index < client.interceptors().size()) {
        // There's another interceptor in the chain. Call that.
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        return client.interceptors().get(index).intercept(chain);
      } else {
        // No more interceptors. Do HTTP.
        return getResponse(request, forWebSocket);
      }
    }
  }

这里贴个图
这里写图片描述
interceptors可以在请求发出去之前,和结果刚拿到之后,对request和response做处理。
处理完之后呢,最终都会走到getResponse这里

  /**
   * Performs the request and returns the response. May return null if this
   * call was canceled.
   */
  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, null);

    int followUpCount = 0;
    while (true) {
      if (canceled) {
        engine.releaseConnection();
        throw new IOException("Canceled");
      }

      try {
        engine.sendRequest();
        engine.readResponse();
      } 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) {
          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) {
          engine = retryEngine;
          continue;
        }

        // Give up; recovery is not possible.
        throw e;
      }

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

      if (followUp == null) {
        if (!forWebSocket) {
          engine.releaseConnection();
        }
        return response;
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (!engine.sameConnection(followUp.httpUrl())) {
        engine.releaseConnection();
      }

      Connection connection = engine.close();
      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, connection, null, null,
          response);
    }
  }

做了一些请求头的默认配置,然后new出一个HttpEngine,通过它来进行

        engine.sendRequest();
        engine.readResponse();

这两步最重要的操作,这两个方法代码很多,就不贴了,sendRequest会先去匹配缓存中的response,然后尝试去调用connect()方法。

  /** Connect to the origin server either directly or via a proxy. */
  private void connect() throws RequestException, RouteException {
    if (connection != null) throw new IllegalStateException();

    if (routeSelector == null) {
      address = createAddress(client, networkRequest);
      try {
        routeSelector = RouteSelector.get(address, networkRequest, client);
      } catch (IOException e) {
        throw new RequestException(e);
      }
    }

    connection = createNextConnection();
    Internal.instance.connectAndSetOwner(client, connection, this, networkRequest);
    route = connection.getRoute();
  }

这里的connectAndSetOwner调用的是OkhttpClient里面的Internal实现,会走到Connection这个类

  /**
   * Connects this connection if it isn't already. This creates tunnels, shares
   * the connection with the connection pool, and configures timeouts.
   */
  void connectAndSetOwner(OkHttpClient client, Object owner, Request request)
      throws RouteException {
    setOwner(owner);

    if (!isConnected()) {
      List<ConnectionSpec> connectionSpecs = route.address.getConnectionSpecs();
      connect(client.getConnectTimeout(), client.getReadTimeout(), client.getWriteTimeout(),
          request, connectionSpecs, client.getRetryOnConnectionFailure());
      if (isFramed()) {
        client.getConnectionPool().share(this);
      }
      client.routeDatabase().connected(getRoute());
    }

    setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
  }

又会走到这里的connect方法

  void connect(int connectTimeout, int readTimeout, int writeTimeout, Request request,
      List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
    if (connected) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
    Proxy proxy = route.getProxy();
    Address address = route.getAddress();

    if (route.address.getSslSocketFactory() == null
        && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
      throw new RouteException(new UnknownServiceException(
          "CLEARTEXT communication not supported: " + connectionSpecs));
    }

    while (!connected) {
      try {
        socket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.getSocketFactory().createSocket()
            : new Socket(proxy);
        connectSocket(connectTimeout, readTimeout, writeTimeout, request,
            connectionSpecSelector);
        connected = true; // Success!
      } catch (IOException e) {
        Util.closeQuietly(socket);
        socket = null;

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }
  }

判断一下代理,ssl配置,然后会走到connectSocket这个方法

  /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
  private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
      Request request, ConnectionSpecSelector connectionSpecSelector) throws IOException {
    socket.setSoTimeout(readTimeout);
    Platform.get().connectSocket(socket, route.getSocketAddress(), connectTimeout);

    if (route.address.getSslSocketFactory() != null) {
      connectTls(readTimeout, writeTimeout, request, connectionSpecSelector);
    }

    if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
      framedConnection = new FramedConnection.Builder(route.address.uriHost, true, socket)
          .protocol(protocol).build();
      framedConnection.sendConnectionPreface();
    } else {
      httpConnection = new HttpConnection(pool, this, socket);
    }
  }

又会走到Platform.get().connectSocket(socket, route.getSocketAddress(), connectTimeout);
当然后面回根据协议作进一步的操作,这里最简单的Http1.1就是

  public void connectSocket(Socket socket, InetSocketAddress address,
      int connectTimeout) throws IOException {
    socket.connect(address, connectTimeout);
  }

绕了这么大弯子,最终就是一个Socket连接。而且针对普通Http连接,包装了一个HttpConnection用于网络请求的读写。

所以,OkHttp并不是对HttpURLConnection的封装,而是自己实现了底层的请求,它的定位更像是HttpClient,HttpURLConnection这些做请求的工具,只是它对I/O读写进行了Okio的包装,并内置了线程池进行异步处理,而且同时支持Https,SPDY等协议

readResponse中的操作,实际上就是从byte流里面将header和body解析出来,当然还有Interceptor的处理。

到此,就分析完了整个执行流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值