OkHttp源码解析

400 阅读8分钟

本文使用的OkHttp版本为3.14.1

为什么使用OkHttp

OkHttp是Square公司开源的网络请求框架,是目前Android使用最广泛的网络框架,从Android4.4开始,HttpURLConnection的底层实现采用OkHttp。

  • 支持HTTP/2并且允许对同一足迹的所有请求共享一个Socket(即套接字,是对TCP传输层/IP网络层的一种具体实现)
  • 如果非HTTP/2,通过连接池减少请求延迟
  • 默认请求Gzip压缩数据
  • 响应缓存,避免重复请求

OkHttp使用方法

标准的OkHttp使用方法分为4个步骤:

  1. 创建Client
  2. 创建Request
  3. ClientRequest包装成Call
  4. 执行Call

Demo.kt

val client = OkHttpClient()

fun get(url: String) {
    val req = Request.Builder()
        .url(url)
        .build()
    val call = client.newCall(req)
    val resp = call.execute() // 同步请求
    
    val body = resp.body()
    System.out.println("$body")

// application/x-www-form-urlencoded
fun post(url: String) {
    val reqBody = FromBody.Builder()
        .add("city", "杭州")
        .add("key", "123123123jfangbfjweinv")
        .build()
    val req = Request.Builder()
        .url(url)
        .post(reqBody)
        .build()
    val call = client.newCall(req)
    ...
}

它的接口封装得很简洁,生成Call后,会交给调度器Dispatcher和拦截器Inspector两个模块依次处理。

  • 调度器:内部维护队列与线程池,完成请求调配
  • 拦截器:完成整个请求过程

HTTP的请求过程是:域名经DNS服务器解析成IP地址 -> 三次握手,建立Socket连接,得到Socket对象 -> 将HTTP报文经Socket写出,开始数据传输

1.png

请求处理流程

分发器/调度器Dispatcher

调度器用来对请求进行调度,将请求直接执行,或者加入等待队列。

newCall()函数返回一个Call对象,Call是一个接口,其实现类为RealCallCall对象通过execute()进行同步流程,通过enqueue()进入异步流程。

异步请求enqueue()

RealCall.java

public void enqueue(Callback callback) {
    synchronized (this) { // 锁住RealCall对象
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(callback); // <--重点:封装为AsyncCall
}

Dispatcher.kt

维护了3个队列。

/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>() // 等待执行的异步队列

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

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>() // 正在执行的同步队列

ArrayDeque内部由数组实现,其优点是:This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue.

异步请求的处理流程图如下:

image.png

Dispatcher拿到Call对象后,是如何判断将其放入ready队列还是running队列?带着问题继续分析。

Dispatcher.java

void enqueue(AsyncCall call) {
  synchronized (this) {
    readyAsyncCalls.add(call);

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.get().forWebSocket) {
      AsyncCall existingCall = findExistingCallWithHost(call.host());
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
    }
  }
  promoteAndExecute();
}

默认所有的Call都会先加入到ready待执行队列,随后执行promoteAndExecute(),扫描队列时有两个限制:

  1. 正在执行的异步请求个数<=64
  2. 同一域名的异步请求个数<=5

同时满足这两个限制时,请求会被交给线程池执行。

private int maxRequests = 64;
private int maxRequestsPerHost = 5;

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));

  List<AsyncCall> executableCalls = new ArrayList<>(); // <--重点1:找出可以执行的Call
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { // <--重点2:扫描ready待执行队列
      AsyncCall asyncCall = i.next();
      // 同时最多64请求并发,防止打满带宽和内存
      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      // 统一域名最多5个请求并发——目的是为服务器限流,但收效有限
      if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

      i.remove(); // 满足了64+5的限制,该请求可以执行
      asyncCall.callsPerHost().incrementAndGet();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }

  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService()); // 丢给线程池执行
  }

  return isRunning;
}

线程池初始化的配置如下:

  • corePoolSize=0,核心线程数为0,意味着空闲时可以回收全部线程
  • maximumPoolSize=Integer.MAX_VALUE,最大线程数为Integer上限,允许在线程池内创建巨多线程
  • keepAliveTime=60s,超过1分钟不再使用的链接会释放
  • SynchronousQueue,容量为0的队列,无法缓存任何东西,一旦有Call丢过来则直接创建线程来执行

对于线程池而言,当一个任务通过execute(Runnable)添加到线程池时:

  • 若线程数量小于corePoolSize,则新建核心线程来处理这个任务
  • 若线程数量大于等于corePoolSize,该任务被添加到等待队列
  • 若无法添加到等待队列(如等待队列已满),则执行以下逻辑
    • 若线程数量小于maximumPoolSize,则新建临时线程执行任务
    • 若线程数量等于maximumPoolSize,使用RejectedExecutionHandler拒绝策略,默认是抛异常
public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}

AsyncCall本身是一个Runnable对象,它的executeOn()函数最终会执行到自身的execute()方法,代码如下。

AsyncCall.java

@Override protected void execute() {
  boolean signalledCallback = false;
  transmitter.timeoutEnter();
  try {
    Response response = getResponseWithInterceptorChain();
    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);
  }
}

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(new RetryAndFollowUpInterceptor(client));
  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));
  ...
}

execute()中通过getResponseWithInterceptorChain()进入到拦截器逻辑,这里按下不表。在try-catch-finally代码块中,finally执行了client.dispatcher().finished(this),其目的是找出下一个将要执行的异步请求,将其放入线程池执行。

void finished(AsyncCall call) {
  call.callsPerHost().decrementAndGet();
  finished(runningAsyncCalls, call);
}

private <T> void finished(Deque<T> calls, T call) {
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    idleCallback = this.idleCallback;
  }

  boolean isRunning = promoteAndExecute();

  if (!isRunning && idleCallback != null) {
    idleCallback.run();
  }
}

以上就是异步请求enqueue()的源码分析。

同步请求execute()

同步请求比较简单,RealCall.execute()会直接把请求丢给任务链,同时调用Dispatcher.executed(this),仅仅是把请求记录在runningSyncCalls中,在finished()时将其移出。

RealCall.java

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.timeoutEnter();
  transmitter.callStart(); // <--重点1:可以实现EventListener接口来计算网络请求耗时
  try {
    client.dispatcher().executed(this); // <--重点2:
    return getResponseWithInterceptorChain();
  } finally {
    client.dispatcher().finished(this);
  }
}

Dispatcher.java

synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

private <T> void finished(Deque<T> calls, T call) {
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    idleCallback = this.idleCallback;
  }

  boolean isRunning = promoteAndExecute();

  if (!isRunning && idleCallback != null) {
    idleCallback.run();
  }
}

拦截器Interceptor

拦截器的入口是 RealCall.getResponseWithInterceptorChain()

RealCall.java

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors()); // <--重点0:自定义拦截器插入在头部
  interceptors.add(new RetryAndFollowUpInterceptor(client)); // <--重点1:按序排列5种自带拦截器
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors()); // <--重点2:自定义网络拦截器插入在倒数第二位
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));
  ...
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
    originalRequest, this, client.connectTimeoutMillis(),
    client.readTimeoutMillis(), client.writeTimeoutMillis());
    ...
  Response response = chain.proceed(originalRequest); // <--重点3:组装成RealInterceptorChain并调用其proceed函数,得到Response
    ...
  return response;

责任链

拦截器采用责任链的设计模式,客户将请求发送到责任链,无需关心请求处理细节。责任链模式的优点是将1.请求的发送者和处理者解耦2.可以自由组装处理者

Android的事件分发机制就是采用的责任链模式。

责任链的处理顺序是:

  1. 输入Request,顺序执行责任链
  2. 发送请求
  3. 接收请求结果
  4. 得到Response,倒序执行责任链

责任链

默认拦截器

OkHttp自带有5种拦截器,可以在它们的基础上增加自定义拦截器。添加自定义拦截器的时机是创建Client的时候,自定义的拦截器有两个插入位置,会插入到责任链的头部or尾部。

  • addInterceptor:添加到责任链条头部
  • addNetworkInterceptor:添加到责任链条倒数第二位,即CallServerInterceptor之前
拦截器交出前执行获得结果后执行
RetryAndFollowUpInterceptor 错误重试与重定向判断用户是否取消了请求根据响应码判断是否需要重定向,如果满足条件则重启所有拦截器
BridgeInterceptor 桥接将HTTP协议必备的请求头加入(如Host)并添加默认行为(如Gzip)保存cookie并解析Gzip
CacheInterceptor 缓存判断是否读缓存(在初始化Client时需要声明缓存目录和大小)判断是否写缓存
ConnectionInterceptor 建立连接找到或者新建连接,并获得socket流
CallServerInterceptor 服务器请求在已经建立的连接中发送HTTP头以及报文解析数据

RealInterceptorChain

这个类的含义是“真正的拦截器”,因为它是组装了所有拦截器后得到的拦截器链,核心逻辑是proceed()函数,它会取出拦截器链头部的Interceptor,执行其proceed()函数,在执行的过程中递归调用,则会依次执行拦截器链第二个、第三个、第N个拦截器。

RealInterceptorChain.java

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
    throws IOException {
  ...
  // 取出第一个interceptor并执行其intercept方法,递归
  RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
      index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
  ...
  return response;
}

分析拦截器的写法,主要关注其intercept()函数,可以分成三个部分:

  1. 执行proceed前,对Request进行处理,此时已经有上一节点处理过(如果存在上个节点)
  2. 执行proceed时,将处理过的Request交给任务链下一节点
  3. 执行proceed后,将任务链下一节点返回的Response进行处理
  4. 最后将处理过的Response返回给任务链上个节点(如果存在)

总结

整个OkHttp的运转过程,可以用这张流程图概括。

image.png

参考资料