一步步带你读懂 Okhttp 源码

前言

okHttp, square 公司开源的网络请求神器,截止到 2019-09-02,在 Github 上面已经超过 34K 的 star,足见他的受欢迎程度。java

到目前为止,他的最新版本是 4.1.0, 使用 kotlin 语言写的,因为本人对 kotlin 语言不是很熟悉,这篇文章已 3.5.0 的版本为基础进行分析。程序员

简介

Rxjava+Okhttp+Refrofit 现在已经成为项目网络请求的首选,在讲解原理以前,咱们先来看一下 Okhttp 的基本使用。web

使用 OkHttp 基本是如下四步:面试

  1. 建立 OkHttpClient 对象
  2. 建立Request请求对象
  3. 建立Call对象
  4. 同步请求调用call.execute();异步请求调用call.enqueue(callback)

同步执行设计模式

//建立OkHttpClient对象
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
   //建立Request请求对象
  Request request = new Request.Builder()
      .url(url)
      .build();

   //建立Call对象,并执行同步获取网络数据
  Response response = client.newCall(request).execute();
  return response.body().string();
}

复制代码

异步执行缓存

void runAsync(String url, Callback callback) {
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request.Builder builder = request.newBuilder().addHeader("name", "test");
            return chain.proceed(builder.build());
        }
    }).build();
    //建立Request请求对象
    Request request = new Request.Builder()
            .url(url)
            .build();

    client.newCall(request).enqueue(callback);

}
复制代码

接下来我会从这四步,分析 Okhttp 的基本原理。bash

OkHttpClient

建立 OkHttpClient 通常有两种方法,一种是直接 new OkHttpClient(),另一种是经过 OkHttpClient.Builder()服务器

OkhttpClient client = new OkHttpClient
                    .Builder()
                    .connectTimeout(5, TimeUnit.SECONDS)
                    .writeTimeout(10,TimeUnit.SECONDS)
                    .readTimeout(10, TimeUnit.SECONDS)
                    .build();
复制代码

第二种建立方式主要是经过建造者模式,来配置一些参数,好比链接超时时间,读写超时时间,超时重试次数等。这样有一个好处,能够对外屏蔽掉构建 client 的细节。关于建造者模式的,有兴趣的能够读个人这一篇文章建造者模式(Builder)及其应用微信

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 对象主要封装的是一些网络请求的信息,好比请求 url,请求方法,请求头,请求 body 等,也比较简单,这里再也不展开阐述。cookie

Call 对象

@Override public Call newCall(Request request) {
  return new RealCall(this, request, false /* for web socket */);
}

复制代码

能够看到 call 对象实际是 RealCall 的实例化对象

RealCall#execute()

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    // 执行 client.dispatcher() 的 executed 方法
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    // 最后再执行 dispatcher 的 finish 方法
    client.dispatcher().finished(this);
  }
}


复制代码

在 execute 方法中,首先会调用 client.dispatcher().executed(this) 加入到 runningAsyncCalls 队列当中,接着执行 getResponseWithInterceptorChain() 获取请求结果,最终再执行 client.dispatcher().finished(this) 将 realCall 从 runningAsyncCalls 队列中移除 。

咱们先来看一下 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);
  }
复制代码

能够看到,首先,他会将客户端的 interceptors 添加到 list 当中,接着,再添加 okhhttp 里面的 interceptor,而后构建了一个 RealInterceptorChain 对象,并将咱们的 List<Interceptor> 做为成员变量,最后调用 RealInterceptorChain 的 proced 方法。

  • client.interceptors() -> 咱们本身添加的请求拦截器,一般是作一些添加统一的token之类操做
  • retryAndFollowUpInterceptor -> 主要负责错误重试和请求重定向
  • BridgeInterceptor -> 负责添加网络请求相关的必要的一些请求头,好比Content-Type、Content-Length、Transfer-Encoding、User-Agent等等
  • CacheInterceptor -> 负责处理缓存相关操做
  • ConnectInterceptor -> 负责与服务器进行链接的操做
  • networkInterceptors -> 一样是咱们能够添加的拦截器的一种,它与client.interceptors() 不一样的是两者拦截的位置不同。
  • CallServerInterceptor -> 在这个拦截器中才会进行真实的网络请求

Interceptor 里面是怎样实现的,这里咱们暂不讨论,接下来,咱们来看一下 proceed 方法

proceed 方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
     
     // 省略无关代码
    

    //  生成 list 当中下一个 interceptot 的 chain 对象
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    // 当前的 interceptor
    Interceptor interceptor = interceptors.get(index);
    // 当前的 intercept 处理下一个 intercept 包装的 chain 对象
    Response response = interceptor.intercept(next);

    // ----

    return response;
  }
复制代码

proceed 方法也很简单,proceed方法每次从拦截器列表中取出拦截器,并调用 interceptor.intercept(next)。

熟悉 Okhttp 的应该都在回到,咱们在 addInterceptor 建立 Interceptor 实例,最终都会调用 chain.proceed(Request request),从而造成一种链式调用。关于责任链模式的能够看个人这一篇文章 责任链模式以及在 Android 中的应用

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request.Builder builder = request.newBuilder().addHeader("name","test");
        return chain.proceed(builder.build());
    }
}).build();

复制代码

而 OkHttp 是怎样结束循环调用的,这是由于最后一个拦截器 CallServerInterceptor 并无调用chain.proceed(request),因此可以结束循环调用。

dispatcher

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

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

  // 异步的请求等待队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  // 异步的正在请求的队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  // 绒布的正在请求的队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  
}
复制代码

异步请求 enqueue(Callback responseCallback)

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}


复制代码

首先,咱们先来看一下 AsyncCall 这个类

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  // ----

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      // 判断请求是否取消了,若是取消了,直接回调 onFailure
      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 {
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}


复制代码
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 继承 NamedRunnable, 而 NamedRunnable 是 Runnable 的子类,当执行 run 方法时,会执行 execute 方法。

咱们再来看一下 dispatcher 的 enqueue 方法

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


复制代码

能够看到当正在请求的请求数小于 maxRequests 的时候而且当前正在请求的队列里面至关 host 的小于 maxRequestsPerHost, 直接添加到 runningAsyncCalls 队列中,并添加到线程池里面执行,不然添加到准备队列 readyAsyncCalls 里面。

当执行 executorService().execute(call) 的时候,会调用 run 方法, run 方法又会调用到 execute 方法进行网络请求,请求完成以后,会调用 client.dispatcher().finished(this) 从队列里面移除。

到此, Okhttp 的主要流程已经讲完


小结

  1. 有一个分发器 Dispatcher,里面有三个请求队列,一个是正在请求的队列,一个是等待队列,另一个是同步的正在请求的队列,当咱们执行 enqueue 方法的时候,他会判断正在请求队列数量是否超过容许的最大并发数量(默认是 64)(线程池的原理),若是超过了,会添加到等待队列里面。 excute 方法是同步执行的,每次执行会添加到同步请求队列当中,执行完毕以后会移除
  2. 设计的核心思想责任链模式,当咱们须要拦截的时候,能够实现 Interceptor 接口,会按照添加的顺序执行 Chain.proceed 方法。
  3. 职责分明,OkhttpClient 对象主要处理一些基础的配置,好比链接超时,读写超时,添加拦截器。Request 主要配置请求方法,请求头等。

推荐阅读

责任链模式以及在 Android 中的应用

观察者设计模式 Vs 事件委托(java)

装饰者模式及其应用

建造者模式(Builder)及其应用

二次封装图片第三方框架——简单工厂模式的运用

Android 二次封装网络加载框架

java 代理模式详解

Rxjava 2.x 源码系列 - 基础框架分析

Rxjava 2.x 源码系列 - 线程切换 (上)

Rxjava 2.x 源码系列 - 线程切换 (下)

Rxjava 2.x 源码系列 - 变换操做符 Map(上)

butterknife 源码分析

一步步拆解 LeakCanary

java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

扫一扫,欢迎关注个人微信公众号 stormjun94(徐公码字), 目前是一名程序员,不只分享 Android开发相关知识,同时还分享技术人成长历程,包括我的总结,职场经验,面试经验等,但愿能让你少走一点弯路。

相关文章
相关标签/搜索