OkHttp

作者: yesyourhighness | 来源:发表于2020-08-12 18:37 被阅读0次

    针对一个新东西,我们首先要有几个疑问

    1. 是什么?
    2. 用来干什么的?
    3. 为什么要用它,有什么优点吗?
      然后才是
    4. 怎么用?
      带着问题思考,看完以下内容,回答问题。

    前言:

    OkHttp是最常用的网络请求框架,Retrofit2也是针对OkHttp的封装,底层还是用的OkHttp。今天就来看看OkHttp的源码,学习一下框架的原理和设计思想。
    这篇文章主要针对OKHttp的工作原理进行分析,着重介绍OKHttp实现的原理以及工作流程。

    原理以及工作流程分析

    首先我们看框架的使用,一步步来分析

    OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象
    Request request = new Request.Builder()
         .url(url)//请求链接
         .get()// 可省略,默认GET方法
         .build();//创建Request对象
    Response response = client.newCall(request).execute();//获取Response对象
    

    以上代码是OkHttp的GET请求的同步请求用法。可以看到
    step1. 创建OkHttpClient对象
    step2. 创建Request请求
    step3. 发起请求,获取请求结果Response。
    我们根据请求流程开始分析OkHttp的工作原理。

    step1.创建OkHttpClient对象
    public OkHttpClient() {
        this(new Builder());
    }
    
    OkHttpClient(Builder builder) {
        this.dispatcher = builder.dispatcher;
        this.proxy = builder.proxy;
        //......
        this.connectTimeout = builder.connectTimeout;
        this.readTimeout = builder.readTimeout;
        this.writeTimeout = builder.writeTimeout;
        this.pingInterval = builder.pingInterval; 
      }
    

    上面是OkHttpClient的构造函数,可以看到OkHttpClient有两个构造函数。根据构造方法的代码,很容易发现在构造方法中主要用builder(这里使用了建造者模式)设置了一些OKHttp的属性。比如:超时设置、拦截器、HTTPS相关等。

    step2. 创建Request请求:
    Request(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers.build();
        this.body = builder.body;
        this.tag = builder.tag != null ? builder.tag : this;
      }
    
    

    可以看到Request也是通过建造者模式创建的,在这里配置了url、method、请求头等信息。

    step3.发起请求

    在OkHttpClient和Request都创建好之后,就开始发起HTTP请求了。OkHttp中请求的方式分为同步请求(client.newCall(request).execute() )和异步请求(client.newCall(request).enqueue())两种,其中同步请求和异步请求的区别就是同步请求会阻塞当前线程,异步请求会放到线程池中执行。

    public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }
    
    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;
      }
    

    可以看到通过newCall()方法创建了RealCall实例,然后通过RealCall.newRealCall()发起请求。接下来我们看异步请求。异步请求调用了RealCall的enqueue()方法。

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

    在这里,OkHttp通过调度器Dispatcher执行请求。

    /**Dispatcher**/
    synchronized void enqueue(AsyncCall call) {
       //这里判断队列是否已满,队列不满怎将请求放到线程池中执行,否则加入到队列中
       if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
         runningAsyncCalls.add(call);
         executorService().execute(call);
       } else {
         readyAsyncCalls.add(call);
       }
     }
    

    可以看到enqueue()方法是一个同步方法(synchronized修饰),在这里首先判断了请求队列是否已满,如果不满,则开始在线程池中执行请求AsyncCall。AsyncCall继承了NamedRunable抽象类,而NamedRunnable继承了Runnable接口,在run()方法中调用了execute()方法。

    protected void execute() {
          boolean signalledCallback = false;
          try {
            //通过责任链模式执行接下来请求任务
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
              signalledCallback = true;
              //执行失败回调
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              signalledCallback = true;
              //执行成功回调
              responseCallback.onResponse(RealCall.this, response);
            }
          } 
          //......
          finally {
            client.dispatcher().finished(this);
          }
        }
    

    在这里开始了OkHttp的核心请求部分。在OkHttp中使用了责任链模式处理这一部分的请求。
    getResponseWithInterceptorChain()开始请求。

    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors()); //自定义的拦截器
        interceptors.add(retryAndFollowUpInterceptor); //重试拦截器,请求失败后重试
        interceptors.add(new BridgeInterceptor(client.cookieJar())); //桥接拦截器,处理请求
        interceptors.add(new CacheInterceptor(client.internalCache())); //缓存拦截器,处理请求缓存
        interceptors.add(new ConnectInterceptor(client)); //连接拦截器,创建HTTP连接
        if (!forWebSocket) {
          interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket)); //网络请求拦截器,开始网络请求
    
        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        return chain.proceed(originalRequest);
      }
    
    OkHttp的拦截器Interceptor

    在上面的代码中OkHttp通过各种拦截器处理请求。那拦截器究竟是什么东西呢?

    public interface Interceptor {
      Response intercept(Chain chain) throws IOException;
    
      interface Chain {
        Request request();
    
        Response proceed(Request request) throws IOException;
    
        /**
         * Returns the connection the request will be executed on. This is only available in the chains
         * of network interceptors; for application interceptors this is always null.
         */
        @Nullable Connection connection();
    
        Call call();
    
        int connectTimeoutMillis();
    
        Chain withConnectTimeout(int timeout, TimeUnit unit);
    
        int readTimeoutMillis();
    
        Chain withReadTimeout(int timeout, TimeUnit unit);
    
        int writeTimeoutMillis();
    
        Chain withWriteTimeout(int timeout, TimeUnit unit);
      }
    }
    

    拦截器是一个接口,各种类型的拦截器实现了这个接口。以下是常见的拦截器:
    . 重试拦截器(RetryAndFollowUpInterceptor):请求在失败的时候重新开始的拦截器。
    . 桥接拦截器(BridgeInterceptor):主要用来构造请求。
    . 缓存拦截器(CacheInterceptor):主要处理HTTP缓存。
    . 连接拦截器(ConnectInterceptor):主要处理HTTP链接。
    . 网络请求拦截器(CallServerInterceptor):负责发起网络请求。
    拦截器是OkHttp发起请求的核心部分,接下来我们针对各种拦截器进行分析。上面的代码中,通过RealInterceptorChain的process()方法开始执行拦截器。

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
        calls++;
        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); //执行拦截器
        //......
        return response;
      }
    

    重试拦截器---RetryAndFollowUpInterceptor
    这里我们以RetryAndFollowUpInterceptor为例,分析RetryAndFollowUpInterceptor的intercept()方法

    public Response intercept(Chain chain) throws IOException {
        //......
        int followUpCount = 0;
        Response priorResponse = null;
        //通过一个循环来重新尝试请求
        while (true) {
          if (canceled) {
            streamAllocation.release();
            throw new IOException("Canceled");
          }
          Response response;
          boolean releaseConnection = true;
          try {
            //1.调用下一个拦截器
            response = realChain.proceed(request, streamAllocation, null, null);
            releaseConnection = false;
          } catch (RouteException e) {
            //......
          } catch (IOException e) {
            //......
          }
          //......
          //2.检测response是否合法
          Request followUp = followUpRequest(response);
          if (followUp == null) {
            if (!forWebSocket) {
              streamAllocation.release();
            }
            //3.返回response,请求完成
            return response;
          }
          //最多尝试20次
          if (++followUpCount > MAX_FOLLOW_UPS) {
            streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
          }
          //4.重新设置请求
          request = followUp;
          priorResponse = response;
        }
      }
    

    在RetryAndFollowUpInterceptor中我们可以看到请求的重试是由一个无线循环保持的,同时在代码里还限制了请求次数,最多20次。RetryAndFollowUpInterceptor的具体逻辑是:

    1. 开启循环,继续调用下一个拦截器直到返回结过;
    2. 通过followUpRequest()方法检查response是否合法,检查逻辑是根据HTTP返回码检测。如果合法followUp为null,则返回结果,否则进行下一步;
    3. 重新设置request、response(用于接下来重新构造response),执行第一步。
    BridgeInterceptor

    我们看看BridgeInterceptor做了哪些事。

    public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();
        RequestBody body = userRequest.body();
        if (body != null) {
          MediaType contentType = body.contentType();
          if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
          }
          long contentLength = body.contentLength();
          if (contentLength != -1) {
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
          } else {
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
          }
        }
    
        if (userRequest.header("Host") == null) {
          requestBuilder.header("Host", hostHeader(userRequest.url(), false));
        }
    
        if (userRequest.header("Connection") == null) {
          requestBuilder.header("Connection", "Keep-Alive");
        }
        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
          transparentGzip = true;
          requestBuilder.header("Accept-Encoding", "gzip");
        }
        List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
        if (!cookies.isEmpty()) {
          requestBuilder.header("Cookie", cookieHeader(cookies));
        }
    
        if (userRequest.header("User-Agent") == null) {
          requestBuilder.header("User-Agent", Version.userAgent());
        }
        Response networkResponse = chain.proceed(requestBuilder.build());
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
        Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
        if (transparentGzip
            && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {
          GzipSource responseBody = new GzipSource(networkResponse.body().source());
          Headers strippedHeaders = networkResponse.headers().newBuilder()
              .removeAll("Content-Encoding")
              .removeAll("Content-Length")
              .build();
          responseBuilder.headers(strippedHeaders);
          String contentType = networkResponse.header("Content-Type");
          responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }
        return responseBuilder.build();
      }
    

    从代码里可以看到,BridgeInterCeptor中设置了请求头的各种参数,比如:Content-type、Connection、User-Agent、GZIP等。

    CacheInterceptor

    缓存拦截器主要是处理HTTP请求缓存的,通过缓存拦截器可以有效的使用缓存减少网络请求。

    public Response intercept(Chain chain) throws IOException {
        Response cacheCandidate = cache != null? cache.get(chain.request()): null;//1.取缓存
        long now = System.currentTimeMillis();
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //2.验证缓存
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse; //获取缓存
    
        if (cache != null) {
          cache.trackResponse(strategy);
        }
        // If we're forbidden from using the network and the cache is insufficient, fail.
        //这里表示禁止使用缓存
        if (networkRequest == null && cacheResponse == null) {
          return new Response.Builder()
              .request(chain.request())
              .protocol(Protocol.HTTP_1_1)
              .code(504)
              .message("Unsatisfiable Request (only-if-cached)")
              .body(Util.EMPTY_RESPONSE)
              .sentRequestAtMillis(-1L)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();
        }
    
        // If we don't need the network, we're done.
        //3.直接返回缓存
        if (networkRequest == null) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
        Response networkResponse = null;
        try {
          //4.没有缓存,执行下一个拦截器
          networkResponse = chain.proceed(networkRequest);
        } 
    
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
    
            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            cache.trackConditionalCacheHit();
            //5.更新缓存
            cache.update(cacheResponse, response);
            return response;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
        //......
        if (cache != null) {
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            //6.保存缓存
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
          }
        }
        return response;
      }
    

    OkHttp会首先取出缓存,然后经过验证处理判断缓存是否可用。流程如下:

    1. 根据请求(以Request为键值)取出缓存
    2. 验证缓存是否可用,可用,则直接返回缓存,否则进行下一步;
    3. 继续执行下一个拦截器,知道返回结果;
    4. 如果之前有缓存,则更新缓存,否则新增缓存。

    https://juejin.im/post/6844903743951994887
    https://juejin.im/post/6844903903033557005#heading-0
    https://www.jianshu.com/p/37e26f4ea57b

    相关文章

      网友评论

          本文标题:OkHttp

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