美文网首页Android进阶之路Android开发
OKhttp源码,Android网络框架精讲,一文带你全方位解析

OKhttp源码,Android网络框架精讲,一文带你全方位解析

作者: 码农的地中海 | 来源:发表于2022-05-31 19:14 被阅读0次

    前言:

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

    本文会将OKHTTP的源码进行拆解,每个部分来单独学习,由简入深,循序渐进,篇幅较长,建议收藏慢慢观看。

    背景:

    在okhttp出现以前,android. 上发起网络请求要么使用系统自带的HtpClient 、HttpURLConnection、 要么使用google开源的Volley、要么使用第三方开源的AsyncHttpClient.随着互联网的发展,APP的业务发展也越来越复杂,APP的网络请求数量急剧增加,但是. 上述的网络请求框架均存在难以性能和并发数量的限制。
    OkHtp流行得益于它的良好的架构设计,强大的拦截器(intercepls)使得操纵网络十分方便; OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求,其源码详见OkHttp Github。
    也得益于强大的生态,大量的流行库都以OkHtp作为底层网络框架或提供支持,比如Retrofit、Glide、 Fresco、Moshi、 Picasso 等。
    当OKhttp面世之后,瞬间成为各个公司的开发者的新宠,常年霸占github star榜单,okhttp可以说是为高效而生,迎合了互联网高速发展的需要

    QQ截图20220531152011.png

    优点

    1. 同时支持HTTP1.1与支持HTTP2.0;
    2. 同时支持同步与异步请求;
    3. 同时具备HTTP与WebSocket功能;
    4. 拥有自动维护的socket连接池,减少握手次数;
    5. 拥有队列线程池,轻松写并发;
    6. 拥有Interceptors(拦截器),轻松处理请求与响应额外需求(例:请求失败重试、响应内容重定向等等)

    源码基于okhttp3 java版本:3.14.9

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

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

    概要:

    QQ截图20220531190113.png

    1、OkHttpClient

    OkHttpClient是什么?

    顾名思义,我们可以理解为是OkHttp的客户端;

    我们来看一下这个类里面有啥东西?

    [图片上传失败...(image-b274eb-1653994768320)]

    首先,从源码里面可以看到声明了一大堆成员变量,咋一看,还有点看不懂,涉及的东西太多了,但是没关系,我们的目的是为了了解这个类的职责;

    下面我们先来看两张图:

    图一:

    QQ截图20220531190138.png

    图二:

    QQ截图20220531190155.png

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

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

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

    至于里面的一些参数是干嘛的,这里我们先不管,只要明白整个类的职责是啥就行了;

    2,Request

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

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

    QQ截图20220531190208.png

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

    再往下看;

    QQ截图20220531190220.png

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

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

    看一下Request的Builder的逻辑:


    QQ截图20220531190230.png

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

    3,Response

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

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

    先来看一下成员变量:

    QQ截图20220531190244.png

    从图片可以看出,Response和Request有点类似,封装了一些参数返回,比如code,message,headers,body等等;

    在来看一下这张图片:

    QQ截图20220531190300.png

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

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

    4,ReallCall

    ReallCall,顾名思义,我们可以先理解为真正的请求,那么我们接下来来看源码求证一下,看看是不是真的是这样;

    首先,先来看一下参数:

    QQ截图20220531190313.png

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

    QQ截图20220531190324.png

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

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

    QQ截图20220531190334.png

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

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

    QQ截图20220531190346.png

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

    QQ截图20220531190356.png

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

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

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

    QQ截图20220531190407.png

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

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

    QQ截图20220531190417.png

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

    5、Dispatcher

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

    接下来我们来看看这个类的职责是做什么的;

    老规矩,先来看看成员变量:

    QQ截图20220531190438.png

    参数不多,我们来一个个介绍:

    • 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)同步请求的执行:

    QQ截图20220531190450.png

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


    QQ截图20220531190459.png

    来看一下这个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) {
          // 1、遍历准备要执行的请求队列
          for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            // 2、判断当前正在执行的请求个数大于最大请求个数时,则取消请求
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            // 3、判断当前主机的连接数超过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()等,不过这些不是重点,了解即可;

    QQ截图20220531190510.png

    6、Interceptor

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

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

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

    但是现实往往是残酷的,网络的环境极其复杂,而每一次的请求也不一定能返回,所以我们需要使用各种策略来保证网络请求可以正常完成,比如重试,缓存等操作来保证网络请求的正常使用;

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

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

    OkHttp的拦截器有:

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

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

    6.1、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方法的逻辑我们大概瞄一眼,就是根据返回码来做一下操作;

    QQ截图20220531190524.png

    6.2、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.3、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.4、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.5、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等等,感兴趣的可以点击获取OKhttp资料学习跟踪源码去分析,这里就不再深入探究了

    相关文章

      网友评论

        本文标题:OKhttp源码,Android网络框架精讲,一文带你全方位解析

        本文链接:https://www.haomeiwen.com/subject/cyzzprtx.html