OkHttp 源码解析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Greathfs/article/details/101602292

前言

OkHttp 现在基本统一了Android网络请求这块领域,我们常用的基本上就是Retrofit+OkHttp了。OkHttp的实现原理和设计思想我们是很有必要学一学的,代码只是工具,思想才是最主要的。
关于HTTP相关的基础知识

OkHttp 的整体框架设计

这里建议把OkHttp源码导入AS中,一个是从github把源码下下来,另一个就是用gradle引入

下面是OkHttp整个的请求流程图:(图片来源网络)
111

OkHttp用法

官方示例

GET 请求:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

POST请求:

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

分析

1.创建 OkHttpClient 对象

OkHttpClient client = new OkHttpClient();

这里我们发现并没有使用OkHttpClient的Builder,为什么呢?我们看下OkHttpClient的构造方法
在这里插入图片描述
在这里插入图片描述
看到这里我们了解到了,如果我们直接new一个OkHttpClient对象,那么我们就相当于使用默认的设置。
如果我们不想使用默认的,则可以自己设置:

mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(loggingInterceptor)
                            .retryOnConnectionFailure(true)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();

下面列举下OkHttpClient的属性:

final Dispatcher dispatcher;//调度器
    final @Nullable
    Proxy proxy;//代理
    final List<Protocol> protocols;//协议
    final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议
    final List<Interceptor> interceptors;//拦截器
    final List<Interceptor> networkInterceptors;//网络拦截器
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;//代理选择器
    final CookieJar cookieJar;//cookie
    final @Nullable
    Cache cache;//cache 缓存
    final @Nullable
    InternalCache internalCache;//内部缓存
    final SocketFactory socketFactory;//socket 工厂
    final @Nullable
    SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
    final @Nullable
    CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
    final HostnameVerifier hostnameVerifier;//主机名字确认
    final CertificatePinner certificatePinner;//证书链
    final Authenticator proxyAuthenticator;//代理身份验证
    final Authenticator authenticator;//本地省份验证
    final ConnectionPool connectionPool;//链接池 复用连接
    final Dns dns; //域名
    final boolean followSslRedirects;//安全套接层重定向
    final boolean followRedirects;//本地重定向
    final boolean retryOnConnectionFailure;//重试连接失败
    final int connectTimeout;//连接超时
    final int readTimeout;//读取超时
    final int writeTimeout;//写入超时

2.发起 HTTP 请求

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实现了Call.Factory,负责根据请求创建新的 Call

callFactory 负责创建 HTTP 请求,HTTP 请求被抽象为了 okhttp3.Call 类,它表示一个已经准备好,可以随时执行的 HTTP 请求

在这里插入图片描述
在这里插入图片描述
我们看一下okhttpClient 如何实现的Call接口,代码如下:

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request);
  }

可以看出 真正的请求交给了RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。

Call是一个接口,我们看下它都有哪些方法:

/**
   * 请求方法
   */
  Request request();

 /**
   * 同步请求方法
   */
  Response execute() throws IOException;

  /**
   * 异步请求方法
   */
  void enqueue(Callback responseCallback);

  /**
   * 取消请求调用
   */
  void cancel();

  /**
   * 判断请求调用是否执行 
   */
  boolean isExecuted();

  /**
   * 判断请求调用是否取消
   */
  boolean isCanceled();

  /**
   * 克隆一个请求调用
   */
  Call clone();

RealCall实现了这些方法

2.1同步请求

我们看下 RealCall的execute方法:

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

主要工作:

  • synchronized (this) 避免重复执行
  • client.dispatcher().executed(this); 实际上调度器只是将call 加入到了同步执行队列中。代码如下:
	//TODO 调度器执行同步请求
    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }
  • getResponseWithInterceptorChain()最核心的代码,请求网络得到响应数据,返回给用户。
  • client.dispatcher().finished(this); 执行调度器的完成方法 移除队列
    可以看出,在同步请求的方法中,涉及到dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。

接下来我们主要说下异步请求

2.2异步请求

我们看下 RealCall的enqueue方法:

@Override public void enqueue(Callback responseCallback) {
    enqueue(responseCallback, false);
  }

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

主要工作:

  • synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的call,可以调用如下方法:进行克隆
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
  @Override public RealCall clone() {
    return RealCall.newRealCall(client, originalRequest, forWebSocket);
  }
  • 利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback)),在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。

这里我们看下Dispatcher的enqueue方法:

//TODO 执行异步请求
    synchronized void enqueue(AsyncCall call) {
        //同时请求不能超过并发数(64,可配置调度器调整)
        //okhttp会使用共享主机即 地址相同的会共享socket
        //同一个host最多允许5条线程通知执行请求
        if (runningAsyncCalls.size() < maxRequests &&
                runningCallsForHost(call) < maxRequestsPerHost) {
            //加入运行队列 并交给线程池执行
            runningAsyncCalls.add(call);
            // AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现
            executorService().execute(call);
        } else {
            // 加入等候队列
            readyAsyncCalls.add(call);
        }
    }

这里我们先看下几个属性值的含义:

	//同时能进行的最大请求数
    private int maxRequests = 64;
    //同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    // 如 https://restapi.amap.com  restapi.amap.com - host
    private int maxRequestsPerHost = 5;
    /**
     * Ready async calls in the order they'll be run.
     * 双端队列,支持首尾两端 双向开口可进可出,方便移除
     * 异步等待队列
     *
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    /**
     * Running asynchronous calls. Includes canceled calls that haven't finished yet.
     *正在进行的异步队列
     */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

executorService()方法就是创建线程池:
在这里插入图片描述
这里我们发现一个新的类AsyncCall,我们看下这个类是什么:

final class AsyncCall extends NamedRunnable
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @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,线程池实际上就是执行了execute(),看下这个方法

final class AsyncCall extends NamedRunnable {
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //责任链模式
        //拦截器链  执行请求
        Response response = getResponseWithInterceptorChain();
        //回调结果
        if (retryAndFollowUpInterceptor.isCanceled()) {
          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!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //移除队列
        client.dispatcher().finished(this);
      }
    }
  }

我们仔细看上面的代码,其实主要就是getResponseWithInterceptorChain()这个方法真正执行请求,获取结果通过 responseCallback 传递给上层使用者

finially里面还有一个finish方法:

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

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

    for (Iterator<AsyncCall> 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.
    }
  }

通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。

接下来就是主要的getResponseWithInterceptorChain这个方法,同步请求和异步请求的原理是一样的,都是在 getResponseWithInterceptorChain() 函数中通过 Interceptor 链条来实现的网络请求逻辑,而异步则是通过 ExecutorService 实现。

// 核心代码 开始真正的执行网络请求
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //责任链
    List<Interceptor> interceptors = new ArrayList<>();
    //在配置okhttpClient 时设置的intercept 由用户自己设置
    interceptors.addAll(client.interceptors());
    //负责处理失败后的重试与重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
    //从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
    //设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //可配置用户自己设置的缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //连接服务器 负责和服务器建立连接 这里才是真正的请求网络
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //配置okhttpClient 时设置的networkInterceptors
      //返回观察单个网络请求和响应的不可变拦截器列表。
      interceptors.addAll(client.networkInterceptors());
    }
    //执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
    //进行http请求报文的封装与请求报文的解析
    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);
  }

getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:

  1. 在配置 OkHttpClient 时设置的 interceptors;
  2. 负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
  3. 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor;
  4. 负责读取缓存直接返回、更新缓存的 CacheInterceptor;
  5. 负责和服务器建立连接的 ConnectInterceptor;
  6. 配置 OkHttpClient 时设置的 networkInterceptors;
  7. 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。

每个Interceptor都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。这样一来,完成网络请求这件事就彻底从 RealCall 类中剥离了出来,简化了各自的责任和逻辑。

2.2.1 CacheInterceptor

CacheInterceptor是okhttp中缓存拦截器,是负责http请求的缓存处理。当从上个拦截器中获取到http请求时,会从缓存里面取出对应的响应(之前缓存过的),如果没有,返回null。然后会根据request和获取到的缓存的response生成一个缓存策略CacheStrategy。

@Override public Response intercept(Chain chain) throws IOException {
//从缓存中取出网络缓存
Response cacheCandidate = cache != null
    ? cache.get(chain.request())
    : null;

long now = System.currentTimeMillis();

//获取到缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

这个缓存策略决定了获取到的缓存response能不能用,需不需要重新去服务器请求数据。

 //从缓存策略中获取到网络请求体(如果缓存不可用,需要用这个Request向服务器请求数据)
    Request networkRequest = strategy.networkRequest;
    //从缓存策略中获取到响应体(之前保存的响应体)
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

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

    //执行到这里表示缓存不可用,需要进行网络请求了
    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());
      }
    }

主要点:

  1. 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest
  2. 如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清楚cacheCandidate缓存。
  3. 如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
  4. 如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。

从后面的拦截器中获取到响应后,需要对响应进行存储,以便下次发起请求时可以直接利用缓存信息。

 // If we have a cache response too, then we're doing a conditional get.
    //如果缓存响应存在
    if (cacheResponse != null) {
      //网络返回的内容没有修改,把网络响应和缓存的响应合并
      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();

    if (cache != null) {
      //当存在响应体并且可以缓存,
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        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;
}

保存缓存的逻辑就比较简单了,主要分两种情况:

第一种情况,缓存已经存在,如果服务器返回的code为304,表示返回的内容没用修改,那就把已用的缓存和网络响应合并然后进行更新缓存。

第二种情况,缓存不存在或者服务器返回的内容发生了改变,在需要缓存的情况下把网络响应内容进行保存

详细解释CacheInterceptor

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

实际上建立连接就是创建了一个 HttpCodec 对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操作的抽象,我们可以叫他解码器。有两个实现:Http1Codec 和 Http2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

在 Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。

而创建 HttpCodec 对象的过程涉及到 StreamAllocation、RealConnection,代码较长,这里就不展开,这个过程概括来说,就是找到一个可用的 RealConnection,再利用 RealConnection 的输入输出(BufferedSource 和 BufferedSink)创建 HttpCodec 对象,供后续步骤使用。

详细解释ConnectInterceptor

2.2.3 CallServerInterceptor

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

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  // 省略部分检查代码

  return response;
}

我们抓住主干部分:

  • 向服务器发送 request header;
  • 如果有 request body,就向服务器发送;
  • 读取 response header,先构造一个 Response 对象;
  • 如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象;

这里我们可以看到,核心工作都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上还是用的 Socket

总结

  1. OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
  2. RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
  3. getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。

参考资料:
https://www.jianshu.com/p/cb444f49a777
https://blog.piasy.com/2016/07/11/Understand-OkHttp/index.html

猜你喜欢

转载自blog.csdn.net/Greathfs/article/details/101602292