OkHttp源码解析

okHttp概述

上一篇文章讲到retrofit网络请求框架,其实retrofit内部并没有真正的实现网络请求,它内部将网络请求封装成了Call,并将网络请求转角给okhttp去执行。okhttp是square开源的一个网络库,将每一个网络请求都封装成一个call,然后利用call去执行网络请求。

下面是官方给的例子:


OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {

  Request request = new Request.Builder()

      .url(url)

      .build();

  Response response = client.newCall(request).execute();

  return response.body().string();

}

okHttp源码阅读

okhttp的使用就像上面的例子一样这么简单,我们看看其内部的实现

调用流程

构造Client

首先用builder模式创建一个okhttpClient实例,里面有一个非常重要的成员dispatcher,顾名思义,主要是用来做okHttp的网络请求分发的。后续我们会再详细介绍。每一个client在创建时,都会直接new出一个dispatcher。


public Builder() {

      dispatcher = new Dispatcher();



    }

构造Request

okHttp的网络请求表示为Request,里面包含了一个请求所需要的参数。


    //请求url

    HttpUrl url;

    //请求方式

    String method;

    //请求头

    Headers.Builder headers;

    //请求body

    RequestBody body;

    //请求tag

    Object tag;

对于okHttp请求的直接触发单位并不是request,而是在request的基础上继续包装了一层,包装为Call。

构造Call

call是一个接口,供我们外部调用,主要有以下几个比较重要的方法


//获取当次请求的Request

Request request();

//同步执行入口

Response execute() throws IOException;

//异步执行入口

void enqueue(Callback responseCallback);

//取消入口

void cancel();

我们实际使用的是其实现类RealCall,我们直接通过client提供的工厂方法,传入request,并返回RealCall。


private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

    this.client = client;

    this.originalRequest = originalRequest;

    this.forWebSocket = forWebSocket;

    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

  }

请求的执行

同步请求 execute


@Override public Response execute() throws IOException {

    synchronized (this) {

      if (executed) throw new IllegalStateException("Already Executed");

      executed = true;

    }

    captureCallStackTrace();

    try {

      //添加到dispatcher的同步队列中

      client.dispatcher().executed(this);

      //通过拦截器链执行请求

      Response result = getResponseWithInterceptorChain();

      if (result == null) throw new IOException("Canceled");

      return result;

    } finally {

        //从dispatcher正在运行队列中中移除

      client.dispatcher().finished(this);

    }

  }

通过局部变量executed来标识一个call只能执行一次,需要用symchronized来保证多线程同步。

然后通过dispatcher的请求处理,其实仅仅是将这个call添加到维护的正在运行的队列中,然后立马通过拦截器链发起请求。


synchronized void executed(RealCall call) {

    runningSyncCalls.add(call);

  }

后面通过拦截器链执行网络请求。

异步请求

异步请求和同步请求非常类似。


@Override public void enqueue(Callback responseCallback) {

    synchronized (this) {

      if (executed) throw new IllegalStateException("Already Executed");

      executed = true;

    }

    captureCallStackTrace();

    eventListener.callStart(this);

    client.dispatcher().enqueue(new AsyncCall(responseCallback));

  }

enqueue仅仅调用了dispatcher的enqueue,需要将RealCall转化为AsyncCall,也就是异步请求Call,其实就是一个Runnable。


final class AsyncCall extends NamedRunnable {

    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {

      super("OkHttp %s", redactedUrl());

      this.responseCallback = responseCallback;

    }

Dispatcher的分发机制

不管同步还是异步请求,okHttp最终都会将请求传递给dispatcher。

dispatcher内部维护三个队列,一个线程池。


private @Nullable ExecutorService executorService;

  private final Deque readyAsyncCalls = new ArrayDeque<>();

  private final Deque runningAsyncCalls = new ArrayDeque<>();

  private final Deque runningSyncCalls = new ArrayDeque<>();

对于同步请求,会直接将请求添加到运行队列中,并马上执行请求,而异步请求可能需要先将请求添加到就绪异步队列中,等待dispatcher的调度唤起。


synchronized void enqueue(AsyncCall call) {

    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

      runningAsyncCalls.add(call);

      executorService().execute(call);

    } else {

      readyAsyncCalls.add(call);

    }

  }

如果当前正在运行的网络请求�数小于设置最大值,那么表示这个请求可以立马发起,则需要将这个call添加到运行的异步队列中,并且调用线程池执行这个请求。否则需要奖这个请求添加到等待队列中。当线程池执行时,其实是调用了这个runnable的run方法,


@Override public final void run() {

    String oldName = Thread.currentThread().getName();

    Thread.currentThread().setName(name);

    try {

      execute();

    } finally {

      Thread.currentThread().setName(oldName);

    }

  }

其实内部就是执行excute方法


  @Override protected void execute() {

      boolean signalledCallback = false;

      try {

        Response response = getResponseWithInterceptorChain(null);

        ....

        responseCallback.onFailure()

        responseCallback.onResponse()

        ....

      } finally {

        client.dispatcher().finished(this);

      }

    }

在run方法执行了execute方法之后,同样是先调用了拦截器链发起网络请求,然后在网络请求回来之后,回调callback,所以,可见这个callBack是在子线程里面做的回调。最后同样是调用finished关闭这个请求。


private  void finished(Deque calls, T call, boolean promoteCalls) {

    int runningCallsCount;

    Runnable idleCallback;

    synchronized (this) {

    //从异步执行队列中移除

      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");

      //唤起下一个call的调用

      if (promoteCalls) promoteCalls();

      //重新计算当前网络请求数

      runningCallsCount = runningCallsCount();

      idleCallback = this.idleCallback;

    }

    if (runningCallsCount == 0 && idleCallback != null) {

      idleCallback.run();

    }

  }

如果是异步执行的call的finish操作,那么它还多了一个非常重要的步骤,通过promoteCalls来唤起下一个call请求发起。


private void promoteCalls() {

    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.

    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {

      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {

        i.remove();

        runningAsyncCalls.add(call);

        executorService().execute(call);

      }

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

    }

  }

首先会判断当前正在请求数是否大于设置的最大请求,然后判断等待队列是否为空,在非空的情况下,从就绪队列中移除首个call,并开始执行。

拦截器链->获取response

上面的同步或者异步请求,最终都会调用到getResponseWithinterceptorChain这个方法上。


Response getResponseWithInterceptorChain(DiskCacheListener diskCacheListener) throws IOException {

    // Build a full stack of interceptors.

    List interceptors = new ArrayList<>();

    interceptors.addAll(client.interceptors());

    interceptors.add(retryAndFollowUpInterceptor);

    interceptors.add(new BridgeInterceptor(client.cookieJar()));

    interceptors.add(new CacheInterceptor(client.internalCache(), diskCacheListener));

    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, this, eventListener, client.connectTimeoutMillis(),

        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);

  }

首先会构造拦截器链,其实就是一个list,将一些需要的拦截器按序添加到list中,然后构造Chain,最终调用proceed开始执行。

链式调用

okHttp这个拦截器的遍历的实现其实是非常巧妙的。Chain有一个index表示当前执行的哪一个拦截器。


//第一次执行

Interceptor.Chain chain = new RealInterceptorChain(.., 0, ...);

    return chain.proceed(originalRequest);

proceed方法


// Call the next interceptor in the chain.

RealInterceptorChain next = new RealInterceptorChain(index + 1);

Interceptor interceptor = interceptors.get(index);

Response response = interceptor.intercept(next);

在proceed中首先会构造出下一个Chain,只需要把index+1即可,然后拿出当前的拦截器,并且执行其intercept方法。


@Override public Response intercept(Chain chain) throws IOException {

....

Response networkResponse = chain.proceed(requestBuilder.build());

....

}

在intercept中,再次调用了下一个链的proceed方法。依次执行,形成了一个链式调用。

拦截器

RetryAndFollow

网络请求失败时的重连和重定向的拦截器

Bridge

桥接拦截器,主要是用来增加请求头,cookied, userAgent。

Cache

主要是负责okHttp的缓存。okHttp有一个缓存策略。

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

    Request networkRequest = strategy.networkRequest;

    Response cacheResponse = strategy.cacheResponse;

networkRequest表示当前请求,cacheResponse表示是否有该请求对应的缓存response。


// If we're forbidden from using the network and the cache is insufficient, fail.

    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 we don't need the network, we're done.

    if (networkRequest == null) {

      return cacheResponse.newBuilder()

          .cacheResponse(stripBody(cacheResponse))

          .build();

    }

如果当前发起的请求为null,而且没有缓存的response,那么直接返回错误。

如果发起的请求不为null,而且存在缓存,那么直接用缓存,提前返回。否则正常发起网络请求。

Connect

ConnectIntercept主要用建立网络链接。


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, chain, doExtensiveHealthChecks);

    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);

  }

  • 首先会从获取出链中拿出stramALlocation。StreamAlloction是okHttp用来辅助管理的一个类,主要是管理流、连接、和call之间的关系。是流和连接之间的component,它会为call请求去寻找连接并建立一个流。这个流是在第一个retryAndFollowIntercept中创建的,最后传递到connectIntercept。

  • 然后通过newStream方法找到一个合适的socket链接。并返回对应的socket操作封装类HttpCodeC,newStream会从连接池中去寻找合适的连接,如果没有可复用的连接,将会创建出新连接。


RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,

          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

CallServer

CallServerIntercept是真正发起网络请求的类,通过上一个拦截器处理完后的HttpCodec进行网络的读写。内部封装了okio进行数据读写


...

//写入请求头

httpCodec.writeRequestHeaders(request);

//通过okIo写入请求请求体

request.body().writeTo(bufferedRequestBody);

然后通过okio进行response读取


if (responseBuilder == null) {

      realChain.eventListener().responseHeadersStart(realChain.call());

      responseBuilder = httpCodec.readResponseHeaders(false);

    }

    Response response = responseBuilder

        .request(request)

        .handshake(streamAllocation.connection().handshake())

        .sentRequestAtMillis(sentRequestMillis)

        .receivedResponseAtMillis(System.currentTimeMillis())

        .build();

okHttp连接池的处理

put

当没有找到可以复用的连接时,需要创建新的连接,并将连接放入连接池中,


void put(RealConnection connection) {

    assert (Thread.holdsLock(this));

    if (!cleanupRunning) {

      cleanupRunning = true;

      executor.execute(cleanupRunnable);

    }

    connections.add(connection);

  }

每次添加新的连接,需要看是否需要清理连接池。这个处理操作是通过线程池来处理的。通过下面几个步骤来判断一个连接是否需要被回收

  • 计数,看一个connect是否被多个streamAllocation所引用。Connecttion内部存储了一个StreamAllocation的弱引用列表list,当一个网络请求结束时,将会关闭stremAllocation,并且回收

for (Iterator i = connections.iterator(); i.hasNext(); ) {

        RealConnection connection = i.next();

        // If the connection is in use, keep searching.

        if (pruneAndGetAllocationCount(connection, now) > 0) {

          inUseConnectionCount++;

          continue;

        }

        idleConnectionCount++;

        }

  • 如果空闲数大于最大空闲数,那么回收

if (longestIdleDurationNs >= this.keepAliveDurationNs

          || idleConnectionCount > this.maxIdleConnections) {

        connections.remove(longestIdleConnection);

      }

get连接


Internal.instance.get(connectionPool, address, this, null);

通过address来从连接池中获取需要复用的连接。


  @Nullable 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;

  }

首先会判断连接池中是否包含了这个请求地址和路由对应的连接,如果有,那么在这个connection中添加streamAllocation的引用并直接返回,

socket

socket连接的建立,是在StreamAllocation中建立的。当我们没找到可以复用的连接,那么将会创建一个新的连接。


result = new RealConnection(connectionPool, selectedRoute);

//为connect添加streamAllocation的引用

acquire(result);

在创建完connection之后,需要建立socket连接


  /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */

  private void connectSocket(int connectTimeout, int readTimeout, Call call,

      EventListener eventListener) 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);

    eventListener.connectStart(call, route.socketAddress(), 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;

    }

    // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0

    // More details:

    // https://github.com/square/okhttp/issues/3245

    // https://android-review.googlesource.com/#/c/271775/

    try {

      source = Okio.buffer(Okio.source(rawSocket));

      sink = Okio.buffer(Okio.sink(rawSocket));

    } catch (NullPointerException npe) {

      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {

        throw new IOException(npe);

      }

    }

  }

上面的就是socket连接的建立过程,大体可以分为以下几步:

  • 创建socket套接字:

rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP

        ? address.socketFactory().createSocket()

        : new Socket(proxy);

  • 连接远端Socket

Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);

  • 读写流,okIo封装

//read

source = Okio.buffer(Okio.source(rawSocket));

//write

sink = Okio.buffer(Okio.sink(rawSocket));

这个是客户端的socket创建,服务端的socket创建需要多一个监听

总结

其实okHttp的利用了java的职责链模式,每一个拦截器都有属于自己功能。下面看看一张自己绘制的okhttp访问的流程。

[图片上传失败...(image-2de00c-1541683717380)]

你可能感兴趣的:(OkHttp源码解析)