写点什么

Android 框架解析:OkHttp 请求原理基本认识

作者:拭心
  • 2021 年 12 月 05 日
  • 本文字数:7640 字

    阅读完需:约 25 分钟

Android 框架解析:OkHttp 请求原理基本认识

大家好,我是拭心,今天我们来学习一下 OkHttp 都具体帮我们做了哪些操作,大概会分三小节来学习它:


  1. OkHttp 请求实现流程基本认识

  2. OkHttp 核心机制深入学习

  3. OkHttp 请求实现流程总结

OkHttp 请求原理基本认识

OkHttp 的使用比较简单,发起一个异步请求的代码如下:


    private void testOkHttp() {
OkHttpClient okHttpClient = getOkHttpClient(); //构造 OkHttpClient Request request = new Request.Builder() .get() //Method GET .url("www.baidu.com") .build(); //构造请求信息
okHttpClient.newCall(request) .enqueue(new Callback() { //发起异步请求 @Override public void onResponse(final Call call, final Response response) throws IOException { //成功拿到响应 int code = response.code(); ResponseBody body = response.body(); String string = body.string(); }
@Override public void onFailure(final Call call, final IOException e) { e.printStackTrace(); } }); }
复制代码


可以看到,使用 OkHttp 发起一个异步请求主要三步:


  1. 需要构造一个 OkHttpClient

  2. 构造请求信息 Request

  3. 发起请求


那么在这简单的代码背后, OkHttp 都做了什么呢?请求是如何被执行的呢?响应是如何拿到的呢?


我们先看一下请求的提交流程

请求的提交流程

我们查看一下 okHttpClient.newCall(request) 方法的源码:


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


newCall(Request) 方法调用了 RealCall.newRealCall() 方法:


static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {  RealCall call = new RealCall(client, originalRequest, forWebSocket);  call.eventListener = client.eventListenerFactory().create(call);  return call;}
复制代码


这个 RealCall.newRealCall()` 方法创建了一个新的 RealCall对象,这个RealCallokhttp3.Call`` 接口的一个实现。


okhttp3.Call 表示一个等待执行的请求,它只能被执行一次,定义了这些方法:


public interface Call extends Cloneable {  //返回这个请求关联的 Request 对象  Request request();
//立即执行请求,阻塞等待拿到响应 Response execute() throws IOException;
//请求入队,异步执行 void enqueue(Callback responseCallback); //取消一个请求 void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory { Call newCall(Request request); }}
复制代码


可以看到,我们前面发起异步请求的 enqueue() 方法是定义在 Call 中的。


okHttpClient.newCall(request)                .enqueue(new Callback() { ...});        //原来就是 Call 的方法
复制代码


在 OkHttp 中,Call 的唯一实现就是 RealCall,它表示一个准备好被执行的请求。和 Request 不同在于,它还提供了发起请求、取消等方法。


在 Retrofit 中也定义了一个 Call 接口,不过它俩层次不一样,相较于 OkHttp.CallRetrofit.Call 增加了更多信息,这个我们后面介绍。


拿到 OkHttp.Call 的实例、RealCall 对象后,我们调用了它的 enqueue() 方法:


//RealCall.enqueue()@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));}
复制代码


核心就在最后这句 client.dispatcher().enqueue(new AsyncCall(responseCallback));,它做了两件事:


  1. 创建一个 AsyncCall 对象

  2. 调用 Dispatcher.enqueue() 方法将请求入队


先看下 AsyncCall 是何方神圣:


final class AsyncCall extends NamedRunnable {  private final Callback responseCallback;
AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; }
String host() { //用于标识这个请求 return originalRequest.url().host(); }
Request request() { return originalRequest; }
RealCall get() { return RealCall.this; }
@Override protected void execute() { //... }}
复制代码


可以看到, AsyncCall 就是一个 Runnable,用于异步执行任务。


接着看 client.dispatcher() 方法,它返回一个调度器 Dispatcher,这是 OkHttp 中比较核心的一个类:


public final class Dispatcher {  private int maxRequests = 64;    //同时最多发起 64 个请求  private int maxRequestsPerHost = 5;    //同一 host 最多发起 5 个请求  private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService; //将会异步创建的线程池
//等待被执行的异步请求队列 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在运行的异步请求队列(其中也包括取消后没有完成的请求) private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在运行的同步请求队列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) { this.executorService = executorService; }
public Dispatcher() { }
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } //...}
复制代码


我们可以得到比较关键的信息如下:


  • 在一个 OkHttpClient 中一般只有一个 Dispatcher,因此一个 OkHttpClient 能发起的最多请求就是 Dispatcher 中定义的 64 个

  • 同样,同一 host 能发起的最多请求是 5 个

  • Dispatcher 中用三个队列保存同步、异步请求

  • 默认线程池核心线程数量为 0,最多数量不限制,消息队列为 SynchronousQueue,因此有请求时会不断创建新线程


然后回到我们之前调用的 Dispatcher.enqueue() 方法:


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


可以看到,调度器在收到一个异步请求后,会先判断当前正在运行的异步请求是否超过默认的 64 个、同一 host 的请求是否小于默认的 5,是的话就开始执行;否则加入等待执行的队列中。


前面我们介绍了 AsyncCall 是一个 NamedRunnable,等它被执行时会调用它的 run() 方法,这个方法调用了 execute() 方法。


public abstract class NamedRunnable implements Runnable {  //...  @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();}
复制代码


到这里我们了解了一个请求从提交到执行背后所经历的,用一张图小结一下:



一个异步请求在发起到执行要经历这么几个状态:


  1. 创建一个 Request:代表用户提交的一个请求的基本信息,包括:URL,请求方法,请求头,请求体,自定义的 CacheControl,tag 等

  2. 创建一个 RealCall:代表准备好被执行的一个请求,在 Request 基础上添加了同步执行、异步执行、取消等操作

  3. 创建一个 AsyncCall:代表可以异步执行的任务

  4. 添加到正在运行/等待运行的异步队列中

  5. 被执行后调用 AsyncCall.execute() 方法


可以看到,OkHttp 中提交一个请求涉及到好几个类,每一个类都有自己独特的职责。我们在设计时,最好也参考这种分层、分阶段的思想,细化粒度,把具体属于某一阶段的功能、信息、方法都封装到单独的类中,然后逐层添加信息。


了解请求发起的流程后,接着看后半部分:如何拿到响应

响应是如何拿到的

前面提到,请求加入队列后,被执行会调用 AsyncCall.execute() 方法,正是在这个方法里调用了我们设置的回调,将结果传给我们:


    //AsyncCall.execute()    @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) {      //...      } finally {        client.dispatcher().finished(this);    //从 dispatcher 里移除当前这个,执行下一个 ①      }    }
复制代码


可以看到,这个方法中先通过 getResponseWithInterceptorChain() 方法拿到了响应 Response,然后进行了回调,最后在 finally 代码块中调用了 client.dispatcher().finished(this) 方法,这个方法的作用是从调度器 Dispatcher 里移除当前这个请求,执行下一个。


我们重点看 getResponseWithInterceptorChain() 方法,这个方法是 OkHttp 的核心,请大家注意了!!



  //RealCall.getResponseWithInterceptorChain()Response getResponseWithInterceptorChain() throws IOException {  // 把我们传入的和 OkHttp 内置的拦截器添加到一个列表里  List<Interceptor> interceptors = new ArrayList<>();  interceptors.addAll(client.interceptors());  //我们自定义的    //内置的 5 个拦截器  interceptors.add(retryAndFollowUpInterceptor);            //重试、取消  interceptors.add(new BridgeInterceptor(client.coJar()));      //桥接  interceptors.add(new CacheInterceptor(client.internalCache()));    //缓存  interceptors.add(new CotInterceptor(client));    //连接  if (!forWebSocket) {    interceptors.addAll(client.networkInterceptors());  }  interceptors.add(new CallServerInterceptor(forWebSocket));  //最后是网络请求
//创建一个 RealInterceptorChain ,传入刚才的拦截器列表 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); //开始执行 return chain.proceed(originalRequest);}
复制代码


可以看到,在这个方法里,主要做了 3 件事:


  1. 把我们构造 OkHttpClient 时传入的拦截器和 OkHttp 内置的 5 个拦截器添加到一个列表里

  2. 创建一个 RealInterceptorChain ,传入刚才的拦截器列表

  3. 调用 RealInterceptorChain.proceed() 方法,这个方法会返回响应


这里提到了 OkHttp 大名鼎鼎的拦截器和拦截器链。很多人觉得 OkHttp 好用的理由之一就是它可以使用拦截器方便的修改请求和响应值,一般我们都会自定义拦截器用来添加请求 Header,或者打印请求、响应信息。


我们来看看这个拦截器链是怎么运转起来的吧。


首先看看 OkHttp 的拦截器和链接口:


public interface Interceptor {  Response intercept(Chain chain) throws IOException;
interface Chain { //当前的请求信息 Request request(); //处理请求,返回结果 Response proceed(Request request) throws IOException; //执行当前请求的连接 @Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit); }}
复制代码


可以看到,拦截器链 Chain 接口中定义了获取当前请求信息和处理请求的方法,然后还定义了一些修改超时时间相隔的方法,比价简单。


拦截器 Interceptor 接口则只有一个方法:


Response intercept(Chain chain) 
复制代码


这个方法的参数是一个拦截器链,返回一个响应。


一般我们自定义一个拦截器时,主要分三步:


  1. 修改请求信息

  2. 调用拦截器链获取结果

  3. 修改响应


比如这样:


public class MyInterceptor implements Interceptor {    @Override    public Response intercept(final Chain chain) throws IOException {        Request originalRequest = chain.request();        //修改请求        Request newRequest = originalRequest.newBuilder()                .addHeader("xx", "xx")                .tag("tag")                .build();
//调用拦截器处理获取结果 Response originalResponse = chain.proceed(newRequest);
//拿到结果进行处理后返回 Response newResponse = originalResponse.newBuilder() .removeHeader("xx") .build(); return newResponse; }}
复制代码


其中最关键的是 Response originalResponse = chain.proceed(newRequest); ,我们去看看 OkHttp 中 Chain 唯一的实现类 RealInterceptorChain 怎么实现的吧。


首先看下 RealInterceptorChain 的成员属性:


public final class RealInterceptorChain implements Interceptor.Chain {  private final List<Interceptor> interceptors;    //所有拦截器  private final StreamAllocation streamAllocation;    //连接引用相关  private final HttpCodec httpCodec;    //底层数据流  private final RealConnection connection;    //连接信息  private final int index;    //当前拦截器的索引  private final Request request;  private final Call call;  private final EventListener eventListener;  private final int connectTimeout;  private final int readTimeout;  private final int writeTimeout;  private int calls;  //...}
复制代码


可以看到,有很多看起来很厉害的类,先别急,StreamAllocation, HttpCodec, RealConnection 我们后面介绍,这里我们只关心两个属性:


  private final List<Interceptor> interceptors;    //所有拦截器  private final int index;    //当前拦截器的索引
复制代码


还记得在前面提到的 RealCall.getResponseWithInterceptorChain() 方法吗:


//RealCall.getResponseWithInterceptorChain()Response getResponseWithInterceptorChain() throws IOException {  List<Interceptor> interceptors = new ArrayList<>();  //...  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,      originalRequest, this, eventListener, client.connectTimeoutMillis(),      client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);}
复制代码


在这个方法里,构造了一个 RealInterceptorChain,同时传入的参数 index 为 0,然后调用了它的 proceed() 方法:


//RealInterceptorChain.proceed()public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,    RealConnection connection) throws IOException {  if (index >= interceptors.size()) throw new AssertionError();
calls++; //...
// 先创建一个新的链,索引是当前加一 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); //然后获取当前的拦截器 Response response = interceptor.intercept(next); //它执行的参数是持有下一个引用的链 //... return response;}
复制代码


RealInterceptorChain.proceed() 中我们看到,除了做一些异常判断,最重要的是上面我截取的部分,做了三件事:


  1. 先创建一个新的链,索引是当前加一(我们姑且叫它“下一个链”)

  2. 根据当前索引从所有拦截器中取出拦截器

  3. 调用拦截器的拦截方法,参数是下一个链


看到这里大家可能会有些懵,这是什么鬼,调用了一个链的处理方法,结果在它里面又给我创建了一个链,这是弄啥嘞!


文字、代码比较抽象,大家看着下面这张图片来听拭心解释:



首先我们从前面的 RealCall.getResponseWithInterceptorChain() 方法可以知道,OkHttp 首先创建了一个 RealInterceptorChain,索引值是 0,这个 RealInterceptorChain 对象的 proceed() 方法会创建一个新的 RealInterceptorChain,索引是 1(我们叫它“第二个链”),然后取出拦截器列表中的第一个拦截器,把第二个链作为参数调用拦截器的 intercept(Chain) 方法。


这时第一个拦截器的拦截方法就被调用了,参数是第二个链,它在处理请求信息后,调用拦截器链的 proceed() 方法,这个方法会再创建一个链(第三个),然后取出第二个拦截器然后调用它的拦截方法(参数是第三个链)。


以此循环,直到索引 index 超出拦截器列表的长度,就不再往下递归调用了,逐层返回结果。


用刘望舒大神的这张图看着更直观:


小结

至此我们对 OkHttp 如何发起请求、逐层处理、拿到响应有了一个基本的认识,三句话概括一下:


  1. 发起异步请求后会构造异步任务 AsyncCall 入队,等被执行时会调用它的 execute() 方法

  2. 这个执行方法会通过拦截器链,挨个调用我们自定义的和系统内置的 5 个拦截器,对请求信息和响应做处理,最后返回结果,回调我们传入的参数

  3. 然后从队列中移除当前任务,执行下一个,以此循环

发布于: 1 小时前阅读数: 16
用户头像

拭心

关注

Never Settle! 2017.11.30 加入

字节跳动高级 Android 工程师,主要从事性能优化相关工作

评论

发布
暂无评论
Android 框架解析:OkHttp 请求原理基本认识