Android面试相关 - Okhttp3源码分析

image.jpg

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

背景

之前的底层网络库基本就是Apache HttpClient和HttpURLConnection。由于HttClient比较难用,官方在Android2.3以后就不建议用了,并且在Android5.0以后废弃了HttpClient,在Android6.0更是删除了HttpClient。

HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序,但是在Android 2.2版本之前存在一些bug,所以官方建议在Android2.3以后替代HttpClient,Volley就是按版本分区使用这两个网络库。

然而随着开源届扛把子Square的崛起,OkHttp的开源,这两个网络库只能被淹没在历史洪流中。Android4.4以后HttpURLConnection的底层已经替换成OkHttp实现。OkHttp配合同样是Square开源的Retrofit,网络请求变得更简便,功能更强大。

介绍

OkHttp是一个现代,快速,高效的网络库,OkHttp 库的设计和实现的首要目标是高效。

1.支持 HTTP/2和SPDY,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接;

2.如果 HTTP/2和SPDY不可用,OkHttp会使用连接池来复用连接以提高效率。

3.支持Gzip降低传输内容的大小

4.支持Http缓存

以上均是摘抄别人的东西

简单使用

   //创建OkHttpClient 对象
   OkHttpClient client = new OkHttpClient();
   //创建request
   Request request = new Request.Builder()
                 .url("")
                 .build();
   //创建请求对象
   Call call = okHttpClient.newCall(request);
   //发起同步请求
   try {
       Response execute = call.execute();
   } catch (Exception e) {
       e.printStackTrace();
   }
   //发起异步请求
   call.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {

      }

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

      }
 });

源码分析

第一步:创建OkHttpClient对象

   OkHttpClient client = new OkHttpClient();

    //另一种方法是通过Builder创建
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    //可以设置一些builder,如设置连接超时时间
    builder.connectTimeout(5000, TimeUnit.SECONDS);
    //初始化OkhttpClient完成
    OkHttpClient okHttpClient = builder.build();

首先我们点击创建的 OkHttpClient 对象进去源码是这样的:

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

然后是走了有参构造:

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }

可以看到有很多常量,这里使用了建造者模式,所以这些常量可以通过 build() 进行配置。如果不进行配置则使用无参构造中传进来的默认配置,每个常量的意思具体如下:

    public Builder() {
      // 分发器Dispatcher对象
      dispatcher = new Dispatcher(); 
      // 协议集合,默认协议集合包括HTTP2,HTTP_1_1 
      protocols = DEFAULT_PROTOCOLS;    
      // 传输层版本和连接协议
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      // 代理选择器
      proxySelector = ProxySelector.getDefault();
      // Cookie瓶,为HTTP的cookies提供策略和持久化
      cookieJar = CookieJar.NO_COOKIES;
      // socket工厂类
      socketFactory = SocketFactory.getDefault();
      // 主机名字确认
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      // 证书链
      certificatePinner = CertificatePinner.DEFAULT;
      // 代理服务器身份验证
      proxyAuthenticator = Authenticator.NONE;
      // 源服务器身份验证
      authenticator = Authenticator.NONE;
      // 连接池
      connectionPool = new ConnectionPool();
      // 域名
      dns = Dns.SYSTEM;
      // 是否遵循 ssl 重定向
      followSslRedirects = true;
      // 是否遵循重定向
      followRedirects = true;
      // 连接失败的时候是否重试  
      retryOnConnectionFailure = true;
      // 连接超时
      connectTimeout = 10_000;
      // 读取超时
      readTimeout = 10_000;
     // 写入超时
      writeTimeout = 10_000;
      // HTTP / 2 和 Web 套接字 ping 之间的时间间隔
      pingInterval = 0;
    }

第二步:创建Request对象

   Request request = new Request.Builder()
                 .url("")
                 .build();

可以看到,这里同样使用了建造者模式,我们点击 Request 进去看看:

  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

Request只是用来设置一些请求链接(url)、请求方法(method)、请求头(headers)、请求体(body)、标签(tag,可作为取消请求的标记)。

第三步:创建请求对象Call

   Call call = okHttpClient.newCall(request);

首先我们点进newCall()方法里去看一下:

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

发现调用的的是RealCall类里面的newRealCall()方法,并传入了了okttpclient和request。
再跟进到newRealCall()方法:

 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.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

发现是创建了一个 RealCall 对象,并返回给上一层。RealCall 是 Call 的实现类,Call 定义了请求相关的操作,例如同步异步、取消请求等方法。所以后续的请求相关操作基本都是在调用 Call 定义的方法,而这些方法真正的执行是它的实现类 RealCall。

最后看看 RealCall 的构造函数,该函数是比较简单的,只是赋值一些常量,然后创建了重试与重定向拦截器(RetryAndFollowUpInterceptor)(这个后面会讲):

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

到这之后我们就可以去发起请求了,像上面的简单使用,可以发起同步请求execute(),或者发起异步请求enqueue(),下面首先说一下同步请求,比较简单,之后再说异步请求。

第四步:同步请求

 Response execute = call.execute();

点进execute()方法进去看一下都做了什么东西:

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

首先我们先看一下刚进入方法的这一个判断,尔值executed表示一个okhttp请求只能运行执行一次

 synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }

接着往下看真正调用请求的是这个方法,分发器的excuted()方法:

      client.dispatcher().executed(this);

点进去会发现调的是Dispatcher类executed()方法,先来看一下Dispatcher,主要是有以下里面的东西:

  //最大请求数是64
  private int maxRequests = 64;
  //最大主机连接数是5
  private int maxRequestsPerHost = 5;
  //线程池
  private @Nullable ExecutorService executorService;
  //准备执行的异步任务队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  //正在执行的异步任务队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  //正在执行的同步任务队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

看完Dispatcher类,继续来看executed()方法:

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

里面的add()方法是将同步请求任务添加到同步请求队列中。

下面就到了最重要的一步,获取请求结果的方法:

      Response result = getResponseWithInterceptorChain();

进入到方法里去看一下:

  Response getResponseWithInterceptorChain() throws IOException {
    // 创建一个拦截器集合,
    List<Interceptor> interceptors = new ArrayList<>();
    //添加用户自定义的interceptors
    interceptors.addAll(client.interceptors());
    //添加retryAndFollowUpInterceptor拦截器,主要负责请求失败重试
    interceptors.add(retryAndFollowUpInterceptor);
    /** 
     *    添加桥拦截器,设置请求头内容类型、长度、编码。添加cookie,设置其他报头,如User-Agent,Host,Keep-alive等。其中 
     *    Keep-Alive是实现多路复用的必要步骤设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
     */    
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
     // 添加缓存拦截器,主要负责cache管理
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 添加连接拦截器,负责获取连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
     // 添加用户自定义的网络拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    // 添加服务器请求拦截器,负责向服务器发起真正的网络请求,并接收到服务器响应以后返回请求结果。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //  构建责任链,通过拦截器构造Interceptor.Chain,其真正实现是RealInterceptorChain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    // 处理责任链中的拦截器,通过拦截器链的proceed()方法获取请求结果
    return chain.proceed(originalRequest);
  }

可以看到,这里用到了很多拦截器,将这些拦截器构建成一条责任链,然后再一个个处理。这里用到了责任链模式,每个拦截器负责相应的功能,上一个拦截器完成会传给下一个拦截器,直到最后一个拦截器执行完再一层层向上返回 Response。
接下来点进proceed()方法砍一下:

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

其他的一些判断就不看了,主要看一下这个:

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

这里会构建一个新的责任链,然后把责任链的索引加 1(为了下次从拦截器集合中取出下一个拦截器),接着从拦截器集合中取出当前拦截器并调用 intercept() 方法,这样如果这个拦截器可以完成任务会马上返回 Response,否则会在 intercept() 方法中继续处理责任链,因为该 intercept() 方法中会继续调用责任链的 proceed() 方法。通过责任链模式,循环调用,最终获取请求结果。

获取结果后还会去调用finished()方法,是在在finally中执行的:

      client.dispatcher().finished(this);

点进去是这样的:

  void finished(RealCall call) {
    //注意传递的是哪个队列,注意boolen值,一个是同步用到的,一个是异步用到的
    finished(runningSyncCalls, call, false); 
  }

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

calls.remove(call) 只是把当前 RealCall 从正在运行的同步请求队列中移除了,说明请求已经完成了。
到这里同步请求的分析就已经完了,下面分析一下异步请求的流程。

第五步:异步请求

 call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

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

            }
        });

进到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));
  }

前几步和同步请求的一样,我们只需要关注一行代码就可以了:

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

下面我们点进去这个方法里面看一下:

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

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //这一块和同步请求的一样
        Response response = getResponseWithInterceptorChain();
       /**
          *  判断重定向和重试拦截器是否取消,如果取消,调用responseCallback的onFailure回调,responseCallback就是我们通过          
          *  enqueue方法传入的Callback对象。如果没取消,调用responseCallback的onResponse回调。
          */
        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 {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

我们发现AsyncCall继承自NamedRunnable,而NamedRunnable实现了Runnable,并且是在NamedRunnable执行了run方法,在这个run()方法中执行了excute()方法也就是AsyncCall中的那个excute()方法。这个excute()方法就不多说了,就是获取请求结果,相信大家一定能看的懂。最后讲一下这个方法:

        client.dispatcher().finished(this);

点进去看一下和同步请求的有什么不同:

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

finished方法内部会将本次的异步请求RealCall从正在请求的异步请求队列中移除,由于promoteCalls传入的是true,接着调用promoteCalls()方法。
进到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.
    }
  }

这个里面做的就是,循环从readyAsyncCalls中取出任务,然后将该任务放到runningAsyncCalls中,最后再调用调用executorService().execute(call)执行任务。

到这里就结束了,历时一周,经过东拼西凑终于是写完了这篇帖子,发现问题的,大家可以留在评论里指出,我在进行修改。

最后,感觉对大家有用的,可以点一下关注,会不定时分享一些安卓相关的知识点。

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