OKHttp源码分析

  之前项目中所有的网络请求都是基于OKHttp完成。之前也多次阅读过源码,但是都没有做过系统的整理。最近工作轻松了下来,终于有时间整理整理,在本文中分析一次OKHttp网络请求的整理流程与原理。
  开始分析代码前我们先来看下OKHttp请求的时序图
[OKHttp网络请求时序图](https://www.processon.com/view/link/5e9ed982e401fd21c1845af6)
OKHttp时序图.png

接下来我们参考时序图来分析一个完整的请求流程

OkHttlpClient客户端的构建

这里使用了构造者模式来生成OKHttpClient对象,部分代码如下

public class OkHttpClient implements Cloneable, Call.Factory {
  ...
  public static final class Builder {
   Dispatcher dispatcher;// 请求事件分发器
    Proxy proxy;// 代理
    List<Protocol> protocols;// 支持的协议
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();// 拦截器
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;// 缓存
    InternalCache internalCache;
    SocketFactory socketFactory;// socket工厂,用户创建&管理socket连接
    SSLSocketFactory sslSocketFactory;// ssl socket工厂,用户创建&管理socket连接
    TrustRootIndex trustRootIndex;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;// 连接池
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    .....
  }
}

在Builder里进行的一些变量的初始化,在这里我们看到两个创建socket的工厂,由此便知道OKHttp是通过socket进行连接的,至于使用socket的这里就不在进行介绍。此外也初始化了一次连接池connectionPool ,使用连接池可以减少对象的反复创建,优化内存的使用。此外进行了Dispatcher 的初始化,我们来看下它的描述:

<p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
of calls concurrently.
具体功能就是 使用一个线程池调度请求。由此我们知道了,OKHttp的网络请求是在线程池中使用的。此外大名鼎鼎的拦截器也会在此进行初始化

Request构建

Request的构建与OKHttpClient相似,同样都是使用构造者模式,部分代码如下,这里就不在做详细说明:

public final class Request {
.....
    public static class Builder {
      private HttpUrl url;// 请求的Url地址
      private String method;// 请求方法 Get 、Post
      private Headers.Builder headers; // 请求头
      private RequestBody body;// 请求体,请求参数
      private Object tag;
      .....
}
......
}

Call的构建

Call的构建是由 okHttpClient.newCall(request)开始,我们来跟下源码,在方法调用之后返回了一个RealCall方法,RealCall中保存了当前OKHttpClient与Request,我们可以认为是对请求进行了进一步封装,方便后续操作。

public class OkHttpClient implements Cloneable, Call.Factory {
   @Override public Call newCall(Request request) {
      return new RealCall(this, request);
  }
}

同步网络请求

在OKHttpClient与Call构建完成之后,接下来就开始进行网络请求,首先我们来分析同步请求。同步请求的入口是 call.execute,从Call的构建中我们了解到返回的是RealCall对象,我们来看下这一步做了什么:

final class RealCall implements Call {
@Override public Response execute() throws IOException {
    synchronized (this) { // 利用线程锁保证线程安全 
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();// 开始连接计时
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);// 将当前请求交给分发器处理
      return getResponseWithInterceptorChain();// 依次经过拦截器,并返回结果
    } finally {
      client.dispatcher().finished(this);// 关闭请求
    }
  }
}

通过源码我们可以看到,请求被提交给了Dispatcher,我们看Dispatcher对同步请求进行了什么操作

public final class Dispatcher {
...
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
....
  /** Used by {@code Call#execute} to signal it is in-flight. */
  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();
    }
  }
...
}

从源码中我们可以看到Dispatcher中有一个runningSyncCalls 队列(队列具有先进先出的特性),在执行时请求Call 被添加到了队列当中。结束时 由于我们在执行同步请求时没有给idleCallback 赋值,所以在结束时仅仅是执行了calls.remove(call))操作,将请求移除队列。看完请求的分发,我们来看下最终的结果,我们看到请求响应是由getResponseWithInterceptorChain()返回,至于里面做了什么处理,由于异步请求最终同样会执行这个方法,所以我们在下一遍文章中再做解释。至此一次同步请求就有了结果。

异步请求

通过时序图我们了解到,异步请求是由 call.enqueue()发起,我们看下源码中做了怎样的处理

final class RealCall implements Call {
...
@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
...
}

最核心的代码 client.dispatcher().enqueue(new AsyncCall(responseCallback));由代码,我们知道请求被再次包装成了AsyncCall,我们来看下AsyncCall这究竟是一个什么,

final class AsyncCall extends NamedRunnable {
...
}
/**
 * Runnable implementation which always sets its thread name.
 */
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();
}

是不是恍然大悟,为什么异步请求是在子线程中进行了,原来是在这里将参数封装进了一个Runnable对象中。 我们接着看,请求被提交给了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 Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }
.....
void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
       .....
      promoteAndExecute();
  }
 ....
}

通过源码我们了解到,当前请求提交之后,请求会被加入到一个预备队列当中准备执行,紧接着调用promoteAndExecute,我们先看下方法介绍

/**

  • Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
    • them on the executor service. Must not be called with synchronization because executing calls
    • can call into user code.
  • @return true if the dispatcher is currently running calls.
    */
    将符合要求的请求从readyAsyncCalls 移动到 runningAsyncCalls,并且在线程池中运行他们,
    不能通过同步调用,因为正在执行调用可以调用用户代码。
    b@return如果调度程序当前正在运行呼叫,则为true
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

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

通过源码我们可以看出,加入正在执行队列的条件是: 当前正在执行的请求数小于最大请求数,且 请求主机数据小于允许的最大请求主机数目。当队列被加速可以执行队列之后,会在线程池中得到执行,
asyncCall.executeOn(executorService());我们接着看AsyncCall.executeOn 源码

 final class AsyncCall extends NamedRunnable {
...
  /**
     * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);// 执行
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

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

通过源码我们可以看到最终会调用到execute()方法,是不是很熟悉,和同步请求的execute 方法很像,同样是调用getResponseWithInterceptorChain()获取结果,只不过不是将结果直接返回,而是通过回调传值。

好了,网络请求的流程已经分析完了,如果有什么不足的地方,还望各位指出。在接下来的一篇文章中,将会介绍OkHttp拦截器的相关源码。谢谢大家

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容