一步步带你读懂 Okhttp 源码

简介: 一步步带你读懂 Okhttp 源码

前言


6afc1a681367c193c579a6988082384e_format,png.png

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


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


简介


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


使用 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 的基本原理。


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 等,也比较简单,这里不再展开阐述。


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

51c314c59e011c20a3352237f5db6d16_format,png.png


而 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)(线程池的原理),如果超过了,会添加到等待队列里面。
  2. excute 方法是同步执行的,每次执行会添加到同步请求队列当中,执行完毕之后会移除
  3. 设计的核心思想责任链模式,当我们需要拦截的时候,可以实现 Interceptor 接口,会按照添加的顺序执行 Chain.proceed 方法。
  4. 职责分明,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


相关文章
|
缓存 Java Android开发
一步步带你读懂 Okhttp 源码
一步步带你读懂 Okhttp 源码
|
存储 人工智能 安全
C++学习必备——文章中含有源码
C++学习必备——文章中含有源码
120 0
C++学习必备——文章中含有源码
|
存储 缓存 JSON
tinydb 源码阅读
TinyDB是一个小型,简单易用,面向文档的数据库;代码仅1800行,纯python编写。TinyDB项目大小刚好,学习它可以了解NOSQL数据库的实现。
435 0
tinydb 源码阅读
|
缓存 网络协议 Java
OkHttp源码详解之二完结篇
OkHttp源码详解之二完结篇
OkHttp源码详解之二完结篇
|
XML Java 测试技术
Spring框架通篇都在用的技术,学完你也能看懂源码
软件开发的生命周期: 从立项到软件停用的过程
113 0
Spring框架通篇都在用的技术,学完你也能看懂源码
|
缓存 编解码 运维
徒手撸了一个RPC框架,理解更透彻了,代码已上传github,自取~
前段时间看到一篇不错的文章《看了这篇你就会手写RPC框架了》,于是便来了兴趣对着实现了一遍,后面觉得还有很多优化的地方便对其进行了改进。
徒手撸了一个RPC框架,理解更透彻了,代码已上传github,自取~
|
Android开发
【Android应用开发】EasyDialog 源码解析(二)
【Android应用开发】EasyDialog 源码解析(二)
137 0
【Android应用开发】EasyDialog 源码解析(二)
|
Android开发
【Android应用开发】EasyDialog 源码解析(一)
【Android应用开发】EasyDialog 源码解析(一)
181 0
【Android应用开发】EasyDialog 源码解析(一)

相关实验场景

更多