Android OkHttp源码解析

·  阅读 1517

使用

这里不详细讲解如何使用,如果需要详细了解使用,请参考我的另一篇文章Android OkHttp3.0 基本使用

我们看一下基本使用

 HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLogger());
        logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        mOkHttpClient = new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .addNetworkInterceptor(logInterceptor)
                .build();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
               
            }
        });
复制代码

下面我们慢慢分析

OkHttpClient.Builder

首先我们通过Builder构建了一个OkHttpClient,Builder是OkHttpClient的一个内部类,其实就是一个Build模式,我们看下Builder类是干什么的

  public static final class Builder {
    Dispatcher dispatcher;
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;
    InternalCache internalCache;
    SocketFactory socketFactory;
    SSLSocketFactory sslSocketFactory;
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
    
    
  public OkHttpClient build() {
      return new OkHttpClient(this);
    }
复制代码

Builder其实就是一个初始化参数的载体,比如你设置的超时时间,拦截器等,然后通过build方法把this赋值给OkHttpClient,也就是把这些初始化的参数交给了OkHttpClient

Request

然后构建一个Request

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }
复制代码

其实Request也是通过内部类Builder构建的,这也是Build模式,他的作用也是一个参数的载体,比如url,method,headers,body

Call

最后通过Call call = mOkHttpClient.newCall(request); 获取Call对象

  ** OkHttpClient.java
  
  @Override 
  public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }
复制代码

内部是创建了一个RealCall对象,我们看一下RealCall就是实际发出请求的对象,他有俩个方法一个是同步请求,一个异步请求

同步请求

  try {
            Response execute = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
复制代码

我们看一下源码

** RealCall.java
 @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  
复制代码

这里做了四件事

  • 判断这个Call是否已经被执行过,每个Call只能被执行一次,如果需要一个完全一样的Call,可以用Call.clone()方法克隆
  • 调用client.dispatcher().executed(this)方法,在同步中,其实这个方法并没有太大作用,只是把Call加入到了一个集合中,然后方便之后的取消,和获取正在运行的Call
  • 然后调用Response result = getResponseWithInterceptorChain();获取Http返回的结果,这个是真正进行请求的方法
  • 最后调用client.dispatcher().finished(this);方法通知Call已经执行完毕

异步请求

  call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
               
            }
        });
复制代码

我们看一下异步请求的源码

**RealCall.java
 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
复制代码
  • 首先还是先判断了是否已经执行过了
  • 然后调用了client.dispatcher().enqueue(new AsyncCall(responseCallback));去执行请求

我们先看一下AsyncCall


  final class AsyncCall extends NamedRunnable {
    ...
    @Override protected void execute() {
    ...
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
          ...
    }
  }
  
 public abstract class NamedRunnable implements Runnable {
  protected final String name;
  ...
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}
复制代码

我们可以看到AsyncCall其实就是一个Runnable,当执行这个Runnable的时候对调用execute方法,而execute方法中还是调用了Response response = getResponseWithInterceptorChain();来进行真正的请求

我们再来分析一下dispatcher


public final class Dispatcher {
  //最大的请求数
  private int maxRequests = 64;
  //每台主机的最大请求数
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  //线程池
  private ExecutorService executorService;

  //准备运行的异步Call
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //正在运行的异步Call
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  //正在运行的同步Call
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

  //异步
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

//同步
 synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

复制代码

我们可以看到他其实维护了一个线程池,和一些存储请求的队列,同步请求是是直接把Call加入到队列中,异步请求是,看当前正在请求的数量来判断加到那个队列中,如果当前运行的请求,小于最大请求数,就用线程池运行这个Call

拦截器

最终我们发现,不管是同步请求还是异步请求,最终都是调用了getResponseWithInterceptorChain()方法,进行的请求,我们来分析一下这个方法

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
复制代码

interceptorOkHttp中很重要的一部分,采用了责任链的设计模式,他把网络请求,缓存,重连,等工能统一了起来,每一个功能都是一个Interceptor,他们在通过Interceptor.Chain环环相扣,最终完成一次网络请求

我们看一下他们到底是怎么环环相扣的

首先上方,把所有的拦截器放入到了一个集合中,构建了第一个拦截器RealInterceptorChain,看他内部做了什么

public final class RealInterceptorChain implements Interceptor.Chain
  ....
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
   ...

    // Call the next interceptor in the chain.
    //构建下一个RealInterceptorChain,注意参数 index + 1
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
        //获取第一个拦截器,并调用第一个拦截器的intercept方法
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   ...

    return response;
  }
复制代码

上方是删减代码,我们看一下,重新构建RealInterceptor,注意参数index + 1,然后获取第一个拦截器,然后调用了interceptor.intercept方法,我们看一下其他的interceptor的intercept方法做了什么,第一个拦截器就是RetryAndFollowUpInterceptor

  ** RetryAndFollowUpInterceptor.java
  
  @Override public Response intercept(Chain chain) throws IOException {
   
   ...
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
   ...
  }
复制代码

这是最关键的代码,他除了走自己的逻辑之外,他还调用 ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);chain是上方传入的next,然后index+1,会让他执行下一个拦截器,如此循环

大概介绍一下每一个拦截器具体做了什么

  • client.interceptors()用户自定义的拦截器
  • RetryAndFollowUpInterceptor负责重连和重定向
  • BridgeInterceptor负责把用户的请求转换为发送到服务器的请求,把服务器的响应转换为用户友好的响应
  • CacheInterceptor负责缓存管理
  • ConnectInterceptor负责和服务器建立连接
  • client.networkInterceptors用户自定义拦截器,仅在网络请求时有用
  • CallServerInterceptor负责向服务器发送请求,从服务器读取响应

我们今天只分析CacheInterceptorConnectInterceptor

CacheInterceptor

 @Override 
 public Response intercept(Chain chain) throws IOException {
    //根据Request获取缓存的Response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根据传入参数,判断缓存的策略,是否使用了网络或者缓存,或者俩者都使用
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    ...

    //请求不使用网络也不使用缓存,则直接返回code=504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // 如果不需要使用网络,则直接把缓存返回
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //交给下一级去请求网络
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
        //如果服务器返回的是304则 返回缓存结果
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //如果不是304,需要缓存,就把请求结果放入缓存中
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // 更新缓存
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
复制代码
  • 首先如果网络和缓存都不用,直接返回504
  • 然后如果不使用不使用网络,则返回缓存中数据
  • 上边都不满足的话,交给下一级去请求网络
  • 如果服务器返回的是304并且有缓存,则返回缓存数据
  • 如果上面都不满足,则返回请求结果,如果需要缓存则缓存数据
  • 缓存数据是用的DiskLruCache

ConnectInterceptor

这个拦截器只是打开了一个链接

 @Override 
 public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
复制代码

这段代码很短,主要工作是,创建了一个HttpCodecRealConnectionHttpCodec的主要作用是编码请求和解码响应,RealConnection是用来向服务器发起链接,这里面最重要就是获取链接的时候用到了连接池ConnectionPool来复用链接

streamAllocation.newStream方法中,经过一系列调用,最终会调用到StreamAllocationfindConnection方法

  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
    ...

      // Attempt to use an already-allocated connection.
      //尝试用已分配的链接,如果链接允许创建新的流则返回链接,通过noNewStreams来判断是否允许创建新的流
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      // Attempt to get a connection from the pool.
      //尝试从链接池中获取一个可用的链接
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }

    // If we need a route, make one. This is a blocking operation.
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }

    RealConnection result;
    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      // Now that we have an IP address, make another attempt at getting a connection from the pool.
      // This could match due to connection coalescing.
      //再次尝试在连接池中获取
      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) return connection;

      // Create a connection and assign it to this allocation immediately. This makes it possible
      // for an asynchronous cancel() to interrupt the handshake we're about to do.
      route = selectedRoute;
      refusedStreamCount = 0;
      //重新建立一个链接
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result);
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    //这里进行TCP和TLS的握手
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // Pool the connection.
      //把新建的链接放入连接池
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    return result;
  }
复制代码

上方的逻辑还是很清楚的

  • 首先看当前链接是否可用,可用则返回
  • 然后从连接池中找可用链接,如果有则返回
  • 再次从连接池寻找可用连接,如果又则返回
  • 重新创建一个链接
  • 进行TCP和TLS握手
  • 把新建的连接放入连接池,然后返回

ConnectionPool 连接池

public final class ConnectionPool {

  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //后台线程用于清理过期链接
  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };
  //存储链接的队列
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;
  //取出链接
  RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }
//放入链接
void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
  
复制代码

其实连接池是维护一个队列储存链接,每次放入链接还会触发后台线程清除失效链接,每次取出都会使用connection.isEligible校验该链接是否可以复用,其实连接池就是省去了建立连接和握手时间

如何进行链接

如果连接池没有合适链接就会重新创建链接并握手, result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled); 我们看一下这个方法到底做了什么

  public void connect(
      int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
    ....
    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {
          connectSocket(connectTimeout, readTimeout);
        }
        //跟TLS握手
        establishProtocol(connectionSpecSelector);
        break;
        ...
    }
    
    
 private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
  }

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

它内部分为是否需要隧道,不需要建立隧道则,则调用connectSocket方法,最终是调用了socket.connect,建立完socket链接之后,需要进行TLS握手,TLS我就不介绍了,我也不熟悉,参考SSL/TLS 握手过程详解

参考:juejin.cn/post/684490…

juejin.cn/post/684490…

blog.piasy.com/2016/07/11/…

分类:
Android
标签: