初级必会之OkHttp源码

976 阅读6分钟

0 前言

作为系列的开篇,还是想解释一下“初级必会”这个标题,并不是凡尔赛!相信点进来的Android小伙伴们都能体会到客户端多难了。各类技术社区随处都能看到Android-er的焦虑:

如今随便找个公司,面试都要来几道算法,左手AMS/WMS/JVM,右手性能优化/JetPack。至于几年前常见于中高级面试中第三方原理,放到现在也就是初级水平了😂。

"万物皆可卷",对未来迷茫的Android工程师建议认真拜读刘望舒大佬的文章。 寒冬已至!视频聊聊四面楚歌的Android工程师该何去何从?

内卷化效应就是长期从事一项相同的工作,并且保持在一定的层面,没有任何变化和改观。这种行为通常是一种自我懈怠,自我消耗。

1 示例代码

//1、创建client对象
OkHttpClient okHttpClient1 = new OkHttpClient();

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build();

//2、client+call 创建 RealCall                
Request request = new Request.Builder()
                .url(URL)
                .build();

Call call = okHttpClient.newCall(request);

//3、同步请求
Response response = call.execute();

//3、异步请求
okHttpClient1.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {
    }

    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

OkHttp请求可以分为三个阶段:

  1. OkHttpClient + Request 构造RealCall;
  2. RealCall同步或异步执行,由Dispatcher分发;
  3. 通过getResponseWithInterceptorChain()获取Response。

2 OkHttpClient

OkHttpClient对象有两种创建方式,一种是直接new出来,使用默认配置,另一种是通过建造者模式自由设置配置参数。

public OkHttpClient() {
    this(new Builder());
}

OkHttpClient类将各种功能模块包装进来,让其对外提供统一API,这种设计模式称为外观模式

创建完client和request后,需要生成一个Call对象。Call对象在OkHttp中可以代表一次请求,通过Call接口提供的各类方法,对此请求进行操作。Call 接口内部提供了内部接口Factory,使用了工厂方法模式

interface Factory {
    Call newCall(Request request);
}

代码中通过client.newCall()获取Call对象。

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

3 RealCall + Dispatcher

RealCall实现Call接口,是发起请求的实现类,下面来看两种不同请求方式的具体实现。

3.1 同步请求

RealCall.class

@Override public Response execute() throws IOException {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //添加
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //移除
      client.dispatcher().finished(this);
    }
}

方法使用了synchronized (this) 来保证同一请求(Call对象)不重复执行,之后利用dispatcher调度器,将请求添加进同步双向队列runningSyncCalls,得到请求结果后,将call从队列中移除。

Dispatcher.class

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

3.2 异步请求

RealCall.class

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

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

与同步请求类似,RealCall中的enqueue方法调用了Dispatcher中具体实现的enqueue,将请求加入到了异步双向队列runningAsyncCalls。这里注意一下入队的条件(不满足条件则进入等待队列):

  1. runningAsyncCalls.size() < maxRequests 并发请求小于64
  2. runningCallsForHost(call) < maxRequestsPerHost 同一个请求数量小于5

下一步,具体的执行交由Dispatcher内部的线程池。挖个坑😥Java线程池有关内容期待后续文章。

Dispatcher.class

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

现在按照“套路”,需要使用getResponseWithInterceptorChain()获取Response拿到请求结果,这部分的实现在哪呢?来看方法中的call对象,它的类型是AsyncCall。似乎有点眼熟,在RealCall中,okHttpClient通过调度器执行enqueue时,为了得到请求结果,之前已经将它new过一次了。

RealCall.class

client.dispatcher().enqueue(new AsyncCall(responseCallback));

既然AsyncCall能拿到请求结果并返回出来,那点进来看看具体的类实现。

RealCall.class

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;
    ...
    @Override
    protected void execute() {
        try {
            Response response = getResponseWithInterceptorChain();
        } catch (IOException e) {
            ...
        } finally {
            client.dispatcher().finished(this);
        }
    }
}

AsyncCall继承了NamedRunnable,本质上其实是一个runnable。这里剔除部分代码,只看请求的主干,可以发现异步请求流程目前已经和同步请求一致了。说清楚了同步异步请求的流程,现在要来说说调度器对异步请求的管理与控制。

3.3 ArrayDeque

Dispatcher.class

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

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

Deque接口继承自Queue接口,支持同时从两端添加或移除元素,因此又被成为双端队列。ArrayDeque是依赖于可变数组来实现的,非线程安全。

前文已经提过,同步请求将请求直接添加到runningSyncCalls中,异步请求根据maxRequests和maxRequestsPerHost的限制条件,选择进入执行队列runningAsyncCalls或等待队列readyAsyncCalls中。

同步请求交由发起请求的线程执行,异步请求交由线程池执行,虽然线程池支持的最大线程数是Integer.MAX_VALUE,但是这个值受maxRequests限制,那么当请求数量达到最大值限制后,后续请求如何调度呢?我们来看回收部分的逻辑,同步异步回收请求时都执行了client.dispatcher().finished(this)方法。

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
}

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
}

同异步请求都调用finished方法,区别只是boolean promoteCalls参数值。

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

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

重点关注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.
    }
}

将此方法的功能概括一下:通过判断请求数量,将等待队列中的请求加入执行队列中,并交由线程池执行。 可以看出,Dispatcher内,线程池主要起到的是缓存、执行作用,主要的调度策略还是由调度器自身完成。

4 InterceptorChain

OkHttp请求的最后一步,通过getResponseWithInterceptorChain()获取Response。这里用到了责任链模式

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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    //执行责任链
    return chain.proceed(originalRequest);
}

创建责任链时用到的是RealInterceptorChain类,实现了Interceptor.Chain接口,并通过调用chain.proceed推动请求进行。下面来看看RealInterceptorChain的实现。

public final class RealInterceptorChain implements Interceptor.Chain {
    ....
    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
            calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    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);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
    }
}

这里其实就是一个递归。每个责任链都调用下一个,推动拦截链执行,最后依次返回Response。可以看到网上出现的Response流程图,既有单向,也有双向,本质上其实都是一致。这部分不具体展开各个Interceptor的作用了,这里只需要知道整个请求过程的调用链条。

网上找了一份流程图供大家参考,图片来源大佬博客拆轮子系列:拆 OkHttp,侵删。

5 最后

这篇博客只梳理了基本的请求流程,OkHttp几个重要的内容还没说:Interceptors和NetworkInterceptors的区别、连接池,这些也算是日常常问的问题了。其中连接池非常重要,但是需要HTTP/TCP的扩展知识,作为“初级必会”本文也就不展开了。后续如果介绍到HTTP,会以此为例。

最后补充一句,也算预告吧,“初级必会”系列其实是想安利几套常用的项目/通信框架(MVP、MVVM、Netty),让有需要的小伙伴能拿来直接上手;第一套是OkHttp+Retrofit+RxJava+Dagger2,后续几篇会把涉及到第三方的源码都做个介绍,最后把框架地址贴出来。

flag:年前把第一套框架全部总结完,给2020画上句号🤪。