手把手带你解读OkHttp源码

339 阅读9分钟

前言

OkHttp 是Android网络访问框架中的扛把子,在初学时只知道如何使用,但并不能做到知其然,知其所以然。然鹅自己去单纯的看源码,又很难领略到他的核心思想。现在就由我来带你走入他深邃的内心世界吧!

OKhttp源码解析

OKhttp异步请求网络的开端

        String url = "http://wwww.baidu.com";
        OkHttpClient okHttpClient = new OkHttpClient();
        final Request request = new Request.Builder()
                .url(url)
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: ");
            }
            

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(TAG, "onResponse: " + response.body().string());
            }
        });

以上是一个基本的网络请求用法,所谓由浅入深,先从OkhttpClient构造方法开始剖析他。

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

可以看到,采用了建造者模式进行了初始化,可以联想到它能够通过这个Builder设置多个属性。那么接下去,我们需要看一下这个builder里面的内容。

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

对应的,我们查看一下他的内部元素

    final Dispatcher dispatcher;//调度器
    final @Nullable
    Proxy proxy;//代理
    final List<Protocol> protocols;//协议
    final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议
    final List<Interceptor> interceptors;//拦截器
    final List<Interceptor> networkInterceptors;//网络拦截器
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;//代理选择器
    final CookieJar cookieJar;//cookie
    final @Nullable
    Cache cache;//cache 缓存
    final @Nullable
    InternalCache internalCache;//内部缓存
    final SocketFactory socketFactory;//socket 工厂
    final @Nullable
    SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
    final @Nullable
    CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
    final HostnameVerifier hostnameVerifier;//主机名字确认
    final CertificatePinner certificatePinner;//证书链
    final Authenticator proxyAuthenticator;//代理身份验证
    final Authenticator authenticator;//本地省份验证
    final ConnectionPool connectionPool;//链接池 复用连接
    final Dns dns; //域名
    final boolean followSslRedirects;//安全套接层重定向
    final boolean followRedirects;//本地重定向
    final boolean retryOnConnectionFailure;//重试连接失败
    final int connectTimeout;//连接超时
    final int readTimeout;//读取超时
    final int writeTimeout;//写入超时

可以看到,我们能够设置的各项属性都在上面了,先放一边,我们继续对网络请求做进一步分析。

关注到这里

    OkHttpClient okHttpClient = new OkHttpClient();
    ...
    Call call = okHttpClient.newCall(request);
    call.enqueue(...

通过newCall方法,获得了一个Call对象。继续点进去查看

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

最终获得了RealCall类,是Call的实现。于是call.enqueue实际调用的是ReallCall的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));//1
  }

在1处我们看到,最后调用了OkhttpClient的dispatcher的enqueue方法。我们回头了解一下Dispatcher是干嘛的。

Dispatcher解析

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

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

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

首先,顾名思义,dispatcher大概就是用于派发任务的一个工具。里面我们可以看到定义了最大请求数,线程池,以及请求、正在运行的异步双端队列和正在运行的同步双端队列。线程池我们看到采用了SynchronousQueue,这是一个容量为0的阻塞队列,任何的插入操作必须等待另一个线程的移除操作,任何的移除操作都必须等待另一个线程的插入操作,并且核心线程为0,但最大线程数为无穷,意味着任务的加入,就会开辟非核心线程执行任务。回到他的enqueue方法中

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

上面的意思简单明了,先尝试给异步线程添加任务,如果大于最大限制数,就添加给等待队列。这里我们看到了AsyncCall,让我们来了解一下它。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    ...

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();//1
        ...
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

看到这里,看到了Callback,就应该明白了回调在这里产生,返回的结果就在1处产生的reponse结果中(此处跳转到OKhttp网络通信模块),在...处进行了结果的回调处理。其次,NamedRunnable继承了Runnable接口,于是明白了,AsycCall就是一个Runnable。那么他的excute方法又为啥要重写呢,我们查看NamedRunnable源码

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

大概作用就是给当前线程重新定个名字,然后用excute方法,来代替run方法的执行。我们可以理解为excute就是run方法。(我猜可能就是为了方法名字的统一吧……是不是和名字里也带个Call,就特别绕)。好了,删除这段记忆,只需要知道他是个Runnable就行了。回到他的excute方法中

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();//1
        ...
      } finally {
        client.dispatcher().finished(this);//2
      }
    }

我们看到标示2处,调用了Dispatcher的finished方法,我们点进去查看一下

  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
  
    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!");//1
      if (promoteCalls) promoteCalls();//2
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

注意标示1处,先是将call从队列中移除,再执行了2中的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(); ) {//1
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);//2
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

在上面1处可以看到,先是从readyAsyncCalls(等待异步运行队列)中取出任务,再看到2处,将取出的任务交给运行队列,同时交付线程池,开辟线程执行任务。至此,Dispatcher这个任务分配器的功能就结束了,那么我们究竟是如何进行网络通信的呢。再次回到AsyncCall的excute方法中

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();//1
        ...
      } finally {
        client.dispatcher().finished(this);//2
      }
    }

此处标示1处即进行了网络的通信,并且将结果用Response保存下来了

OKhttp网络通信——interceptors解析

我们查看一下getResponseWithInterceptorChain()的源码吧!

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //TODO 责任链
    List<Interceptor> interceptors = new ArrayList<>();
    //TODO 在配置okhttpClient 时设置的intercept 由用户自己设置
    interceptors.addAll(client.interceptors());//1
    //TODO 负责处理失败后的重试与重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
    //TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
    //TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //TODO 可配置用户自己设置的缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //TODO 配置okhttpClient 时设置的networkInterceptors
      //TODO 返回观察单个网络请求和响应的不可变拦截器列表。
      interceptors.addAll(client.networkInterceptors());//2
    }
    //TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
    //TODO 进行http请求报文的封装与请求报文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 创建责任链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

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

这里出现了极其重要的类Interceptor——拦截器。先进行解释一下,首先在1处,添加自定义的Interceptor,比如一些json解析,日志打印等功能型拦截器。再添加OKhttp自带的拦截器。注意2处标示,此处是指在访问通过网络时,才添加的拦截器(毕竟有些结果可以通过缓存得到,就不需要重复拦截)。接下去我们看到执行了Chain的proceed方法,而Chain的实现类RealInterceptorChain,因此我们直接去看后者的proceed方法的神奇之处。

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;//1
  ...
  
  ...
@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // 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);//2

    return response;
  }
}

首先看到1处,每一个Chain都持有所有的interceptors。因此只要指定拦截器的位置,他就能够找到那个拦截器。此处只是单纯的找到下一个interceptor。重点来了,看2处,此处传给当前interceptor的是下一个编号的Chain。那么执行的过程就可以理解成如下

1 执行0号chain的proceed方法(即getResponseWithInterceptorChain()方法中,创建的首个Chain),找到第0号interceptor,并创建1号chain给他

2 第0号interceptor执行intercept方法,同时执行1号chain的proceed方法,找到第1号interceptor,并创建2号chain给他.

3 第1号interceptor执行intercept方法,同时执行2号chain的proceed方法,找到第2号interceptor,并创建3号chain给他.

以此类推,直到最后,而每一个Chain则类似信息包,包含了index,处理后的request等等信息,向后传递。是责任链模式的一种很好的应用

同时,每个Interceptor重写的intercept方法中,必须调用chain.proceed(request)方法,将链式继续调用。而Interceptor中不同的写法也会产生不同的效果。

OKhttp同步请求网络

先附上源码

  @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);//1
      Response result = getResponseWithInterceptorChain();//2
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  //接1处源码
    synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

同步请求与异步请求的区别在于,他虽然也调用了Dispatcher的excute方法,但是仅仅只是把RealCall传入runningSyncCalls队列当中,并未使用线程池去操作他,而后调用getResponseWithInterceptorChain()进行网络访问,与异步请求没有差异。

总结

先附上最终的总结流程图

本文中详细介绍了OKHTTP运行的主体思想与过程,但是并未分析不同拦截器的细节问题,比如缓存拦截器,网络拦截器与普通拦截器请求时机的不同,但是把最主要的思想描述出来了。待有空了继续补充文章(现阶段主要为了准备面试)。如果有问题,麻烦各位在留言中指出!