Android 网络框架之OkHttp源码解析

前言:OkHttp框架是Android的网络请求框架,无数的项目都在使用着这个框架,重要性不言而喻;html

  • 本文会将OKHTTP的源码进行拆解,每一个部分来单独学习,由简入深,按部就班,篇幅较长,建议收藏,慢慢观看,若是以为内容不错的话,点赞关注来一波,感谢!

源码基于okhttp3 java版本:3.14.9java

  • OkHttp3的简单使用:
public void request() {
        String url = "http://wwww.baidu.com";
        OkHttpClient okHttpClient = new OkHttpClient();
        final Request request = new Request.Builder()
                .url(url)
                .get() //默认就是GET请求,能够不写
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: ");
            }

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

咱们将使用到的这些类拆分出来,逐个讲解;android

概要:

一、OkHttpClient

OkHttpClient是什么?git

顾名思义,咱们能够理解为是OkHttp的客户端;github

咱们来看一下这个类里面有啥东西?编程

首先,从源码里面能够看到声明了一大堆成员变量,咋一看,还有点看不懂,涉及的东西太多了,可是不要紧,咱们的目的是为了了解这个类的职责;设计模式

下面咱们先来看两张图:api

图一:缓存

图二:服务器

怎样,看了上面两张图,是否是有种熟悉的感受,是否是很像咱们写Gson解析时的bean类;

没错,这个OkHttpClient里面就是这样的代码,相似于bean类,主要作一些参数的赋值与获取;

那么到这里咱们就明朗了,OkHttpClient里面并无很复杂的逻辑,整个类的逻辑反而很清晰,就是实现参数的管理;

至于里面的一些参数是干吗的,这里咱们先无论,只要明白整个类的职责是啥就好了;

2,Request

Request,这个单词的意思是请求,那么这个类里面是作的这个操做吗?

咱们来看一下源码,一窥究竟;

先来看一下这个成员变量,不多,就几个,可是从命名能够看出,这里面定义了请求的URL,请求的method,还有请求头headers,请求体body等参数;

再往下看;

从这里能够看出,源码里面也是定义了一些参数的赋值与获取,那么也是和bean类的逻辑差很少;

这里面其实没没作啥特殊操做,主要是经过建造者模式,根据参数来建立Request类;

看一下Request的Builder的逻辑:

这里只用做示例,没必要关心代码细节,咱们只须要了解这个类的职责,就是根据请求的参数构建实体类;

3,Response

Response,顾名思义,为请求的返回体;

那么这个类里面究竟实现了什么逻辑呢?

先来看一下成员变量:

从图片能够看出,Response和Request有点相似,封装了一些参数返回,好比code,message,headers,body等等;

在来看一下这张图片:

也是定义了参数的赋值与获取,这里就再也不赘述;

Response类里面主要定义了服务器返回的相关信息,这里面也没有其余的复杂逻辑,把它当成一个bean类来理解便可;

4,ReallCall

ReallCall,顾名思义,咱们能够先理解为真正的请求,那么咱们接下来来看源码求证一下,看看是否是真的是这样;

首先,先来看一下参数:

从图片能够看出,只有几个参数,都是在构造方法初始化的;

这里面初始化了Transmitter,这个咱们后面再讲;

那么咱们再来看一下它这里面有哪些方法:

这个几个方法有没有熟悉的感受,这里实际上是经过桥接设计模式,实际调用的是OkHttpClient和Transmitter的函数;

这几个方法是网络请求的最核心的方法,execute()方法其实是同步请求,在当前线程就触发了网络请求,经过getResponseWithInterceptorChain()来执行网络请求从而获取到Response返回结果;

而enqueue(Callback responseCallback)方法,是在子线程中执行的,经过建立一个AsyncCall,传递给client.dispatcher(),这里面主要是线程的执行逻辑;

这个AsyncCall是实现了Runnable接口,具体执行是在AsyncCall的execute()方法里面;

咱们来看一下AsyncCall的execute()方法具体实现逻辑;

这里面主要有两个方法,一个是executeOn(),经过入参传递一个线程池,来执行当前线程,另外一个方法是execute(),为异步的具体实现,让咱们来看看这个方法作了啥?

和上面同步调用的逻辑同样,也是经过getResponseWithInterceptorChain()来执行网络请求从而获取到Response返回结果;

还有这个方法getResponseWithInterceptorChain(),经过拦截器+责任链设计模式来实现网络请求,这个咱们下面再介绍,这个只要了解是经过这个来进行网络请求的便可;

小结: ReallCall的职责是进行真正的网络请求,里面封装了两个方法,一个是同步请求execute(),一个是异步请求enqueue(),还有最后也是最重要的一个方法getResponseWithInterceptorChain(),经过这个方法来执行网络请求;

五、Dispatcher

咱们在看ReallCall源码的时候,频频见到这个方法,client.dispatcher().调用,这个方法的真面目就是Dispatcher,这个Dispatcher是在建立OkHttpClient的时候进行初始化的;

接下来咱们来看看这个类的职责是作什么的;

老规矩,先来看当作员变量:

参数很少,咱们来一个个介绍:

  • maxRequests:最多存在64个请求;
  • maxRequestsPerHost:每一个主机最多同时请求数为5;
  • idleCallback:程序空闲时的回调;
  • executorService:线程池;
  • readyAsyncCalls:即将要进行的异步请求;
  • runningAsyncCalls:正在进行的异步请求;
  • runningSyncCalls:正在进行的同步请求;

下面来说一讲这个类几个比较重要的方法;

(1)线程池的建立:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
复制代码

核心线程为0,每隔60秒会清空空闲的线程,而最大线程无限制,可是已经经过成员变量来进行控制了,没啥影响;

(2)同步请求的执行:

同步请求的执行是这个方法,具体调用是在ReallCall的excute()方法里面,在Dispatcher先经过调用executed(),后面再调用finish()方法来移除同步请求;

来看一下这个finish()方法;

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();
    }
  }
复制代码

这个方法的逻辑很简单,先移除队列里的call,而后再调用promoteAndExecute()执行已经准备好执行的请求,这个逻辑咱们下面再讲;

(3)异步请求的执行:

void enqueue(AsyncCall call) {
    synchronized (this) {
    // 添加请求到异步队列;
      readyAsyncCalls.add(call);

      if (!call.get().forWebSocket) {
        // 判断当前请求是否已经存在
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        // 若是当前请求已经存在,则复用以前的线程计数,不进行递增;
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    // 执行请求
    promoteAndExecute();
  }
复制代码

这个方法里面,先将请求添加到异步队列readyAsyncCalls里面,而后再调用promoteAndExecute()方法来触发请求,下面咱们来看看promoteAndExecute()的逻辑;

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.
        // 三、判断当前主机的链接数超过5个时,则跳过当前请求;
        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;
  }
复制代码

这个promoteAndExecute()方法的主要逻辑是执行readyAsyncCalls队列里的请求,而maxRequests和maxRequestsPerHost也是在这里处理的;

除此以后,这个类里面还有一些查询的方法,好比queuedCalls(),runningCalls()等,不过这些不是重点,了解便可;

六、Interceptor

拦截器,能够说是OkHttp最重要的部分了,这一部分经过一个很巧妙的设计,将复杂的网络请求逻辑分散到每一个拦截器中,这种设计模式就叫作责任链

责任链模式的优势:下降耦合度,简化对象,加强对象的灵活性;
缺点:性能会受到必定的影响;

那么对于网络请求,最简单的实现就是从0到1,也就是我直接请求,不考虑异常因素,保证每次请求都能成功,那么这个实现就很简单,只须要调用请求网络的api进行请求数据便可;

可是现实每每是残酷的,网络的环境极其复杂,而每一次的请求也不必定能返回,因此咱们须要使用各类策略来保证网络请求能够正常完成,好比重试,缓存等操做来保证网络请求的正常使用;

而OkHttp的网络请求经过责任链设计了几个拦截器,巧妙的经过责任链模式来处理复杂的网络请求,避免了类的臃肿而且提供了很好的扩展性,缺点就是当责任链上的对象过多时,可能会出现性能的问题;

那么接下来咱们来看看OkHttp的拦截器的实现吧;

OkHttp的拦截器有:

  • RetryAndFollowUpInterceptor:失败和重定向拦截器;
  • BridgeInterceptor:封装Response的拦截器;
  • CacheInterceptor:缓存处理相关的拦截器;
  • ConnectInterceptor:链接服务的拦截器,真正的网络请求在这里实现;
  • CallServerInterceptor:负责写请求和读响应的拦截器;

下面咱们来一个个具体分析;

6.一、RetryAndFollowUpInterceptor

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      // 准备链接请求
      transmitter.prepareToConnect(request);
	  ...
      Response response;
      boolean success = false;
     
      // 执行其余拦截器的功能,获取Response;
      response = realChain.proceed(request, transmitter, null);

   	  // 根据Response的返回码来判断要执行重试仍是重定向;
      Request followUp = followUpRequest(response, route);
	  ...
      if (followUp == null) {
        // 若是followUpRequest返回的Request为空,那边就表示不须要执行重试或者重定向,直接返回数据;
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        // 若是followUp为null,请求体不为空,而且只须要请求一次时,那么就返回response;
        return response;
      }

  	  // 判断重试或者重定向的次数是否超过最大的次数,是的话则抛出异常;
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
	  // 将须要重试或者重定向的请求赋值给新的请求;
      request = followUp;
    }
  }
复制代码

followUpRequest方法的逻辑咱们大概瞄一眼,就是根据返回码来作一下操做;

6.二、BridgeInterceptor

桥接拦截器,这里主要作网络请求的封装,用于简化应用层的逻辑,好比网络请求须要传"Transfer-Encoding","Accept-Encoding","User-Agent","Cookie"这些参数,可是应用层不须要关心这些,那么就由这个拦截器来作这些封装;

当请求完成以后,也会对Response的header作一下封装处理,返回给应用层,这样应用层就不须要关心这个header的细节,简化操做;

public Response intercept(Chain chain) throws IOException {
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
     // 处理封装"Content-Length","Transfer-Encoding","Host","Connection","Accept-Encoding","Cookie","User-Agent"等请求头;
     
	// 执行后续的拦截器的逻辑
    Response networkResponse = chain.proceed(requestBuilder.build());

  	// 获取返回体的Builder
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
	...
    //处理返回的Response的"Content-Encoding"、"Content-Length"、"Content-Type"等返回头;
    ...
    return responseBuilder.build();
  }
复制代码

6.三、CacheInterceptor

CacheInterceptor是缓存处理的拦截器,咱们先来看一下这个拦截器的逻辑;

public Response intercept(Chain chain) throws IOException {
	// 先获取候选缓存,前提是有配置缓存,也就是cache不为空;
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
	// 获取缓存策略;
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    // 缓存策略的请求类
    Request networkRequest = strategy.networkRequest;
    // 获取的本地的缓存;
    Response cacheResponse = strategy.cacheResponse;

    // 若是被禁止使用网络数据且缓存数据为空,那么返回一个504的Response,而且body为空;
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          ...
          .build();
    }

    // 若是不须要使用网络数据,那么就直接返回缓存的数据;
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

	// 执行后续的拦截器逻辑;
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      ...
    }

    if (cacheResponse != null) {
      // 若是缓存数据不为空而且code为304,表示数据没有变化,继续使用缓存数据;
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder().xx.build();
        ...
        // 更新缓存数据
        cache.update(cacheResponse, response);
        return response;
      }
    }

	// 获取网络返回的response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    // 将网络数据保存到缓存中;
    CacheRequest cacheRequest = cache.put(response);
    return cacheWritingResponse(cacheRequest, response);

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          // 缓存失效,那么就移除缓存
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
复制代码

在这个获取缓存策略这一步,会生成一个CacheStrategy对象,用于管理缓存策略,那么在将缓存策略以前,咱们先来了解几个概念;

强缓存:在请求数据的时候,查看请求头expires和cache-control是否命中缓存,若是是的话,那么久就会从缓存中获取数据,不会走网络请求;

协商缓存:而协商缓存是在没有命中强缓存的状况下才会走的逻辑,必会走一次网络请求,经过last-modified和etag返回头判断是否命中缓存,若是没有命中,那么就走网络从新获取到数据,协商缓存须要服务器支持才能实现;

那么接下来咱们来看一下OkHttp是怎么实现缓存策略逻辑的;

若是对于缓存逻辑不是很清楚的话,能够看一下这篇文章:Android 你不得不学的HTTP相关知识

从上面那个方法,咱们来看看new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()的逻辑;

private CacheStrategy getCandidate() {
      
      // 若是不使用缓存,那么就返回一个空的Response的CacheStrategy;
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      // 强缓存
      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      // 判断强缓存是否有效,是的话就返回缓存数据;
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }

      // 协商缓存
      String conditionName;
      String conditionValue;
      if (etag != null) {
        // etag协商缓存
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        // Last-Modified协商缓存
        conditionName = "If-Modified-Since";
        // 最后修改时间
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        // Last-Modified协商缓存
        conditionName = "If-Modified-Since";
        // 服务器最后修改时间
        conditionValue = servedDateString;
      } else {
        // 没有协商缓存,返回一个空的Response的CacheStrategy;
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

	  // 设置header
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
复制代码

6.四、ConnectInterceptor

这个拦截器比较重要,网络的最底层实现都是经过这个类,这里面封装了socket链接和TLS握手等逻辑,接下来咱们来看看具体是怎么实现的;

public Response intercept(Chain chain) throws IOException {
    ...
    // 这简单的一行代码,却实现了无比复杂的网络请求,Exchange用于下一个拦截器CallServerInterceptor进行网络请求使用;
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
	// 执行后续的拦截器逻辑
    return realChain.proceed(request, transmitter, exchange);
  }
复制代码

这个类主要实现了如下几个步骤的逻辑:

  • 1:调用transmitter.newExchange方法;
  • 2:经过Transmitter的ExchangeFinder调用了find方法;
  • 3:ExchangeFinder里调用了findHealthyConnection方法;
  • 4:ExchangeFinder里调用了findConnection方法建立了RealConnection;
  • 5:调用了RealConnection的connect方法,实现了TCP + TLS 握手,底层经过socket来实现的;
  • 6: 经过RealConnection的newCodec方法建立了两个协议类,一个是Http1ExchangeCodec,对应着HTTP1.1,一个是Http2ExchangeCodec,对应着HTTP2.0;

这个拦截器主要是实现网络链接的逻辑,而网络请求的逻辑是放在CallServerInterceptor这个拦截器中实现的;

那么上面咱们讲完这个拦截器的基本逻辑,下面咱们来看看更深层次的知识;

对于链接来讲,最简单的实现就是每次须要的时候都进行建立并链接,不须要考虑网络环境,以及资源等等因素,可是现实状况咱们不可能这样作,由于若是要追求极致的体验,咱们就必须得作优化;

而这里的优化就是链接复用;

下面咱们来看看源码是怎么实现复用逻辑的,上面咱们了解到建立RealConnection是经过ExchangeFinder的findConnection方法,那么咱们来看看这个方法里的具体逻辑;

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    ...
    synchronized (connectionPool) {
      ...
      if (result == null) {
        // 尝试从缓存池中获取可用的链接
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
          // 若是获取不到,那么久从Route里面获取
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
          // 若是当前的路由是重试的路由,那么就从路由里面获取
          selectedRoute = transmitter.connection.route();
        }
      }
    }
   
    if (result != null) {
      // 若是获取到链接,那么就直接返回结果
      return result;
    }

    // 若是前面没有获取到链接,那么这里就经过RouteSelector先获取到Route,而后再获取Connection;
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      //  经过RouteSelector获取Route
      routeSelection = routeSelector.next();
    }

    // 
    List<Route> routes = null;
    synchronized (connectionPool) {
      if (newRouteSelection) {
        // 经过RouteSelector拿到Route集合(IP地址),再次尝试从缓存池中获取链接,看看是否有能够复用的链接;
        routes = routeSelection.getAll();
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          ...
        }
      }

      if (!foundPooledConnection) {
        // 若是上面没有获取到,那么就建立一个新的链接
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }
    
    // 开始TCP握手和TSL握手,这是一个阻塞的过程;
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      connectingConnection = null;
      // 将链接成功的RealConnection放到缓存池中,用于后续复用
      connectionPool.put(result);
      transmitter.acquireConnectionNoEvents(result);
    }
    
    return result;
  }
复制代码

好了,上面的链接复用已经讲完了,下面咱们来看看链接的具体逻辑,也就是connect的方法的具体逻辑;

public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
 	
    if (route.requiresTunnel()) {
      // 若是是HTTP的代理隧道,那么就会走代理隧道的加载逻辑;
      connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
    } else {
      // 走正常的链接逻辑,无代理
      connectSocket(connectTimeout, readTimeout, call, eventListener);
    }
    // 创建协议,这里会触发TLS的握手
    establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
   
复制代码

这个方法主要分三步:

  • 第一步:若是判断设置了代理,那么就会走有代理的方法connectTunnel,底层经过socket实现;
  • 第二步:若是没有设置代理,那么就走默认的无代理链接模式,底层经过socket实现;
  • 第三部:创建协议,这里会触发TLS握手的调用;

那么接下来咱们来看看TLS握手的具体实现,调用方法是RealConnection的connectTls方法;

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    
      // 建立SSLSocket,用于链接;
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // 配置SSLSocket的密码,TLS版本和扩展信息
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      
      // 开始TLS握手
      sslSocket.startHandshake();
      // 获取sslSocketSession,用于创建会话
      SSLSession sslSocketSession = sslSocket.getSession();
      Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

      // 验证目标主机的证书
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
        ...
      }

    // 检查服务器提供的证书是否在固定证书里面
    address.certificatePinner().check(address.url().host(),
        unverifiedHandshake.peerCertificates());

    // 链接成功,保存握手信息和ALPN协议
    String maybeProtocol = connectionSpec.supportsTlsExtensions()
        ? Platform.get().getSelectedProtocol(sslSocket)
        : null;
      ...
  }
复制代码

这个connectTls方法主要作了几个操做:

  • 第一步:建立SSLSocket,用于创建链接;
  • 第二步:开启TLS握手;
  • 第三步:验证目标主机的证书;
  • 第四步:检查服务器提供的证书是否在固定证书里面;
  • 第五步:链接成功,保存握手信息和ALPN协议;

第四步若是看不懂的,能够参考一下这个连接,写的很详细;

SSL Pinning on Android

若是对于TLS握手还不是很清楚的,能够看一下我以前写的这篇文章:

Android 网络编程之HTTPS详解

6.五、CallServerInterceptor

在上一个拦截器ConnectInterceptor里,咱们已经和服务器创建起链接了;

那么接下来就是想服务器发送header和body以及接收服务器返回的数据了,这些逻辑都在这个拦截器中;

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

	// 将请求头写入到socket中,底层经过ExchangeCodec协议类(对应Http1ExchangeCodec和Http2ExchangeCodec),最终是经过Okio来实现的,具体实如今RealBufferedSink这个类里面
    exchange.writeRequestHeaders(request);

   // 若是有body的话,经过Okio将body写入到socket中,用于发送给服务器;
    BufferedSink bufferedRequestBody = Okio.buffer(
        exchange.createRequestBody(request, true));
    request.body().writeTo(bufferedRequestBody);
       
    // 底层经过ExchangeCodec协议类(对应Http1ExchangeCodec和Http2ExchangeCodec)来读取返回头header的数据;
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

	...
    
	// 建立返回体Response;
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
	
    ...
    
 	// 底层经过ExchangeCodec协议类(对应Http1ExchangeCodec和Http2ExchangeCodec)来读取返回体body的数据;
    response = response.newBuilder()
        .body(exchange.openResponseBody(response))
        .build();
  
    return response;
  }
复制代码

这个类的逻辑就是socket相关的操做了,在这里经过Okio将数据写入到socket和从socket中读取服务器返回的数据;

OkHttp的源码到这里就大体分析完了,固然还有一些细节没有涉及到,好比DNS,cookie等等,感兴趣的能够本身跟踪源码去分析,这里就再也不深刻探究了;

其余

Android 你不得不学的HTTP相关知识

Android 网络编程之TCP、UDP详解

Android 网络编程之HTTPS详解

关于我

兄dei,若是个人文章对你有帮助的话,请帮我点个赞吧️,也能够关注一下个人Github博客;

相关文章
相关标签/搜索