OkHttp3 源码分析

OkHttp3 源码分析

基于okhttp3.14.9

源码学习,先理清脉络,然后再深入的针对每一个小的知识点进行探究即可,切勿捡了芝麻丢了习惯

通常我们使用OkHttp进行网络访问,主要包含以下三步
一. 创建Request请求对象,封装请求相关的信息(url,method,body,headers)
二. 创建OkHttpClient对象,负责帮助我们将请求执行
三. 执行请求,设置对应的回调监听

// 一. 创建Request请求对象
Request request = new Request.Builder()
    .url("https://api.github.com/")
    .build();
    
// 二. 创建OkhttpClient对象
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new HttpLoggingInterceptor())
    .cache(new Cache(cacheDir, cacheSize))
    .build();
    
// 三. 执行请求
// 方式一 同步执行
okhttp3.Response execute = okHttpClient.newCall(request).execute();
// 方式二 异步执行
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
    @Override
    public void onFailure(okhttp3.Call call, IOException e) {
        
    }

    @Override
    public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {

    }
});

上述两种方式分别是同步和异步执行请求,我们都分析下

一、 Request请求创建

// 1. 创建Request.Builder()
public Builder() {
    dispatcher = new Dispatcher(); // 创建调度器,稍后在看
    protocols = DEFAULT_PROTOCOLS; // 默认协议 http/1.1 & http/2
    connectionSpecs = DEFAULT_CONNECTION_SPECS; // TSL连接 ,未加密、未认证的http连接
    eventListenerFactory = EventListener.factory(EventListener.NONE); // 事件监听工厂
    proxySelector = ProxySelector.getDefault(); // 代理选择器
    cookieJar = CookieJar.NO_COOKIES; 
    socketFactory = SocketFactory.getDefault(); // Socket工厂
    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; // 连接超时时间10s
    readTimeout = 10_000; // 读取超时时间10s
    writeTimeout = 10_000; //   写入超时时间10s
    pingInterval = 0; 
}

步骤 1 .这里是通过Request.Builder, 使用建造者模式, 初始化相关的信息,最后统一转化为Request对象,稍后详细分析

// 2. 设置Url
public Builder url(String url) {
    if (url == null) throw new NullPointerException("url == null");
    
    // 1.将websocket协议中的WS协议和WSS协议转换成了http协议和Https协议
    // Silently replace web socket URLs with HTTP URLs.
    if (url.regionMatches(true, 0, "ws:", 0, 3)) {
    url = "http:" + url.substring(3);
    } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
    url = "https:" + url.substring(4);
    }
    
    // 2.将url转化为HttpUrl,这里的细节比较多,我们无需关注太多
    HttpUrl parsed = HttpUrl.parse(url);
    if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
    return url(parsed);
}

步骤 2 中,我们会把请求的url中websocket协议转化成http或https协议,然后使用HttpUrl进行转化,下面我们详细的说下转化过程

// 3. HttpUrl对于url的转化工作
public final class HttpUrl{
    public static HttpUrl get(String url) {
        return new Builder().parse(null, url).build();
    }
    
    Builder parse(@Nullable HttpUrl base, String input) {
      int pos = skipLeadingAsciiWhitespace(input, 0, input.length());
      int limit = skipTrailingAsciiWhitespace(input, pos, input.length());
      ...
      // Scheme
      // Authority.
      // Resolve the relative path.
      // Query.
      // Fragment.
    }
}

关于步骤 3 ,这里简单的说明下其主要作用,大家可以对照源码逐行进行分析

  1. 使用pos 和 limit 记录下 url真正开始和结束的地方
  2. 解析协议或者认证格式,将pos移动到协议或认证后
    2.1 解析协议scheme(http, https)
    2.2 解析url,存储其中用户名、密码、host域名、端口号和请求路径,比如(username:password@host:port),由于用户名,密码和端口号是可选的,所以可能是下面这种格式([username[:password]@]host[:port])
  3. 解析url 中?后面请求参数,移动pos
  4. 解析url 中#后面的网页标识符,
// 4. 将builder中的参数封装到Request对象
public Request build() {
    if (url == null) throw new IllegalStateException("url == null");
    return new Request(this);
}

public final class Request {
    final HttpUrl url;
    final String method;
    final Headers headers;
    final @Nullable RequestBody body;
    final Map<Class<?>, Object> tags;
    Request(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers.build();
        this.body = builder.body;
        this.tags = Util.immutableMap(builder.tags);
    }
}

步骤 4 中,讲builder中解析的url, 请求方式method,请求headers, 请求体body等封装到Request对象中,下面我们继续看一下主流程二中OkHttpClient.Builder是如何创建OkHttpClient的

二、 OkHttpClient对象创建

// 5. OkHttpClient.Builder初始化
public Builder() {
    dispatcher = new Dispatcher(); // 请求调度器,默认最大64个请求
    protocols = DEFAULT_PROTOCOLS; // 协议http2, http1.1
    connectionSpecs = DEFAULT_CONNECTION_SPECS; // 网络连通性协议tsl
    eventListenerFactory = EventListener.factory(EventListener.NONE); // 网络连接状态监听
    proxySelector = ProxySelector.getDefault(); // 网络代理
    if (proxySelector == null) {
    proxySelector = new NullProxySelector();
    }
    cookieJar = CookieJar.NO_COOKIES; // cookie
    socketFactory = SocketFactory.getDefault(); // Socket
    hostnameVerifier = OkHostnameVerifier.INSTANCE; // 域名校验
    certificatePinner = CertificatePinner.DEFAULT; // 证书校验
    proxyAuthenticator = Authenticator.NONE; // 认证
    authenticator = Authenticator.NONE;
    connectionPool = new ConnectionPool(); // 连接池
    dns = Dns.SYSTEM; // DNS解析
    followSslRedirects = true;
    followRedirects = true;
    retryOnConnectionFailure = true;
    callTimeout = 0;
    connectTimeout = 10_000; // 连接超时时间10秒
    readTimeout = 10_000; // 接收超时时间10秒
    writeTimeout = 10_000; // 发送超时时间10秒
    pingInterval = 0;
}

步骤 5 . 我们可以看到Builder的构造方法中,对OkHttpClient的相关的参数做了默认的初始化,最终build()时传给了OkHttpClient。中途还有一些添加拦截器的操作,下面我们仔细的看下方式一请求的实现

三 、 执行请求

okHttpClient.newCall(request).execute()

  // 6.准备一个将要再将来执行的请求Request
  /**
   * 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 */);
  }

步骤 6: 我们先来看一下RealCall的父类CallCall是一个准备好的可被执行的Request,如果被取消了就无法再次被执行,它内部有获取原始请求,同步异步执行请求,取消请求,克隆请求,和判断请求状态方法

// 7. Call是一个准备好的用来执行的Request
public interface Call extends Cloneable {
  //  返回原始的请求对象
  Request request();

  //  同步阻塞的返回响应,这个只代表传输层成功,并不一定是应用层成功(404,500)
  Response execute() throws IOException;

  // 异步的执行请求,将请求放入请求队列等待执行
  void enqueue(Callback responseCallback);

  // 取消一个请求,如果请求已经执行完成是无法被取消的
  void cancel();


  boolean isExecuted();

  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

在 7 中,我们看一下RealCall类,RealCall的三个参数分别是我们的Client、Request,和是否是websocket(默认false),然后会通过client的事件监听工厂为RealCall创建一个回调监听

// 8. 创建了RealCall的实现
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
}

步骤 8 中创建了准备好的用来执行的请求RealCall, 初始化了对应的发射器transmitter

// 9. RealCall初始化
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

步骤 9 中, RealCall的构造方法中主要是记录了client, 原始的request对象,并为client创建了重试和跟踪拦截器

// 10. RealCall 执行
@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);
    }
}

步骤 10 :RealCall的执行excute方法。其核心我们可以看到是通过把Call放入到调度器内部的数组

// 11. Dispatcher
public final class Dispatcher {
  
  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);
  }
}

步骤11 :继续向下,我们会发现调度器Dispatcher,会把请求Call放入双端队列,

后续真正去同步执行的是通过getResponseWithInterceptorChain()方法,并返回响应结果,这一步开始之前,我们先说一下拦截器

  • RetryAndFollowUpInterceptor
    在网络请求失败后进行重试
    当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接

  • BridgeInteceptor
    设置内容长度,内容编码
    设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
    添加cookie
    设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤

  • CacheInterceptor
    当网络请求有符合要求的Cache时直接返回Cache
    当服务器返回内容有改变时更新当前cache
    如果当前cache失效,删除

  • ConnectInterceptor
    为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。

  • CallServerInterceptor
    负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回

拦截器的执行流程

okhttp拦截器.png

这一步Response result = getResponseWithInterceptorChain(); 就是执行拦截器链,直到返回Response:

// 12.执行
 Response getResponseWithInterceptorChain() throws IOException {
    // 12.1 构建拦截器列表集合
   // 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));

    // 12.2 把拦截器列表封装到为真正的执行拦截器链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      // 12.3 开始执行,并返回响应Response对象
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

步骤 12 中,会逐步添加各种类型的拦截器,然后将所有的拦截器全部封装到RealInterceptorChain中,最终调用其proceed方法,来执行请求,并返回Response对象。下面我们继续看下proceed方法具体做了哪些操作

// 13 .
public final class RealInterceptorChain implements Interceptor.Chain {
   @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
  
  // 13. 核心的执行过程
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
      
    ......

    // Call the next interceptor in the chain.
    // 13.1 index从0开始,获取创建下一个拦截器链
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 13.2 取出当前的拦截器
    Interceptor interceptor = interceptors.get(index);
    
    // 13.3 执行当前拦截器返回对应的response
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
   ...

    return response;
  }
}

步骤 13 , 最终的请求就是通过轮询整个interceptors,不断的创建每一个interceptor对应的chain,然后执行返回响应结果,这里采用的是经典的责任链模式。总结拦截器封装和执行过程如下

  1. 封装所有的拦截器到拦截器列表list<Interceptor> interceptors
  2. 创建第一个拦截器执行链RealInterceptorChain chain,传入拦截器列表interceptors,默认当前index=0,并调用拦截器链chain的proceed处理方法
  3. 拦截器链chain内部会取出1中封装的拦截器列表中下一个拦截器,并将其封装为下一个拦截器链next; 然后取出当前index=0的拦截器,用其去执行拦截器next
  4. 所以除了CallServerInterceptor拦截器,其它拦截器内部都需要调用chain的proceed方法,来保证执行当前下一个拦截器任务

备注:关于response获取到响应后,会逐级逆向执行相关的拦截器,后续再补充

异步执行

okHttpClient.newCall(request).enqueue(callback)

以上分析的是同步请求,异步请求也是大同小异:

// 1. 放入队列
@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

继续跟踪到Dispatcher中

// 2. Dispatcher中加入队列,使用executorService去执行AsyncCall
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

Dispatcher的enqueue函数,先判断是否异步请求队列长度大于线程池最大请求数,以及当前主机的请求数超过5个。如果没有将给异步call加入到异步线程队列,调用线程池执行该call,如果超了,将该异步call加入到异步等待队列,

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

AsynCall是RealCall内部类,继承于NameRunnable,NameRunable其实就是Runnable的子类,定义了一个execute方法,执行在run()方法中。将AsyncCall加入到线程池,既然AsyncCall是一个Runnnable,那么就是执行Async的execute方法。

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();  // 调用拦截器链,执行请求 返回response
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          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); // 完成了请求
      }
    }

在一次网络请求不管成功失败,都会调用finally中的这行代码client.dispatcher().finished(this); 别问我为啥?跟踪一下这行代码:

/** Used by {@code AsyncCall#run} to signal completion. */
  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!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

将异步call熊runningAsyncCalls队列中移除,然后 如果是异步请求就会执行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.
    }
  }

其实就是查看线程池情况,然后从readAsyncCall是中获取等待的异步call执行,如此循环,直到所有的异步call执行完成

未完待续。。。
如有问题,欢迎指正

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

推荐阅读更多精彩内容

  • 在上一篇 Retrofit 学习第三弹—源码分析篇 分析了 Retrofit 的源码,分析到请求 Call 位置,...
    Kip_Salens阅读 886评论 0 2
  • 重点 本文打算从三点来剖析OkHttp3: 网络请求的整理流程-会使用 拦截器模式-易扩展 缓存和连接池-高性能 ...
    lycknight阅读 367评论 0 3
  • OkHttp3官网 okhttp3的使用如下: 1.okhttp3引用--在build.gradle中增加引用 2...
    Android_小马范儿阅读 368评论 0 0
  • 一.简介 OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Andr...
    雷涛赛文阅读 657评论 1 1
  • OkHttp3是目前Android热门的网络请求框架之一,本篇来分析一下OkHttp3最主要的几部分: 同步、异步...
    h2coder阅读 425评论 0 2