一篇文章带你熟悉OkHttp

作者: 树獭非懒 | 来源:发表于2018-12-12 12:41 被阅读17次

    一、Okhttp的简单使用

    使用步骤

    1. 构建网络请求控制对象OkHttpClient
    2. 构建请求对象request
    3. 创建Call对象
    4. 创建接收返回数据的对象response
    5. 发送网络请求

    第一步和第二步我都用了“构建”这个词,这是因为这两个对象内部都是通过建造者设计模式来创建的。

    当请求准备好了后,就开始建立和服务器的连接。连接成功后,执行最后两步,也就是就是发送请求和接收返回数据。

    1.下面是一个同步请求的示例代码
            //1.构建OkHttpClient对象
            OkHttpClient okHttpClient=new OkHttpClient();  
            Request.Builder builder=new Request.Builder();  //2.构建一个请求对象request,比如请求的url,请求方式
            Request request=builder.url("http://ip.taobao.com/service/getIpInfo.php?ip=223.68.134.166")
                    .get()
                    .build();
            //3.创建一个Call对象
            final Call call= okHttpClient.newCall(request);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                    //4.发送请求,并接收回复的返回数据
                        Response response=call.execute();    
                        Log.d("response", "run: "+response.body().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    

    同步请求是什么意思呢?
    简单的说就是有多个请求的情况下,一次只能处理一个请求,只有获得这次请求返回的响应时,才能发送下一个请求。

    OkHttp的请求有两种方式:同步和异步请求

    两者在使用方式上最大的不同在于最后一步调用的方法:
    同步请求调用的是 execute方法,异步请求调用的是 enqueue方法

    2.发送异步请求的代码示例
    //发送异步请求
    call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.d(TAG, "异步请求失败"+e.toString());
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.d(TAG, "异步请求成功"+response.body().string());
                }
            });
    

    但它们在实现方式上确有很大的不同。

    关于Okhttp的更多使用方法请看这里:
    OkHttp用法

    二、源码解析

    1.核心控制类

    从流程的步骤可以看出OkHttpClient是我们的核心控制类,它控制着整个网络请求,就拿它当切入口

    先看它的定义的一大堆东西(这里先随便过一眼就好了)

        final Dispatcher dispatcher;  //分发器
        final Proxy proxy;  //代理
        final List<Protocol> protocols; //协议
        final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议
        final List<Interceptor> interceptors; //拦截器
        final List<Interceptor> networkInterceptors; //网络拦截器
        final ProxySelector proxySelector; //代理选择
        final CookieJar cookieJar; //cookie
        final Cache cache; //缓存
        final InternalCache internalCache;  //内部缓存
        final SocketFactory socketFactory;  //socket 工厂
        final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS
        final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。
        final HostnameVerifier hostnameVerifier;    //  主机名字确认
        final CertificatePinner certificatePinner;  //  证书链
        final Authenticator proxyAuthenticator;     //代理身份验证
        final Authenticator authenticator;      // 本地身份验证
        final ConnectionPool connectionPool;    //连接池,复用连接
        final Dns dns;  //域名
        final boolean followSslRedirects;  //安全套接层重定向
        final boolean followRedirects;  //本地重定向
        final boolean retryOnConnectionFailure; //重试连接失败
        final int connectTimeout;    //连接超时
        final int readTimeout; //read 超时
        final int writeTimeout; //write 超时
        final int pingInterval; //ping的间隔时间
    

    接下来看它的构造方法

    public OkHttpClient() {
        this(new Builder());
      }
    
      OkHttpClient(Builder builder) {
        this.dispatcher = builder.dispatcher;
        this.proxy = builder.proxy;
        this.protocols = builder.protocols;
        this.connectionSpecs = builder.connectionSpecs;
        //.....、此处省略大量代码
      }
    

    可以看出来它使用了建造者模式。我们可以针对性选择它在Builder里定义的参数进行设置,如果不设置它的参数,就会使用它的默认值,像这样

    public Builder() {
          dispatcher = new Dispatcher();
          protocols = DEFAULT_PROTOCOLS;
          connectionSpecs = DEFAULT_CONNECTION_SPECS;
          eventListenerFactory = EventListener.factory(EventListener.NONE);
          proxySelector = ProxySelector.getDefault();
          //.....、此处省略大量代码
          }
    

    同样Request对象也是通过建造者模式构建的

    public final class Request {
      final HttpUrl url;  //请求的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);
      }
    

    把控制类对象和请求对象都准备好了后,我们需要创建一个Call对象,进入源码我们可以看到它其实是一个接口,那我们就需要找它的实现类-RealCall,它才是真正的请求执行者。

    通过调用newCall方法,创建RealCall对象

    public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
      }
      
      static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
      }
    

    到这里,都算是为我们正式请求之前的准备工作。其实,真正的请求在背后做了什么,才是Okhttp的优秀之处,精华所在。同时也是重难点所在,接下来,才是真正的大餐。

    2.拦截器机制

    在继续分析源码之前,先介绍一个很重要的概念-拦截器机制。okhttp就是通过拦截器机制来获取和处理网络请求响应的。

    先从三个层看这个图

    OkhttpInterceptor.png

    在图中,可以看到在请求和获取响应时,都会经过Okhttp的核心,并且都会经过一系列OkHttp自带的拦截器(图中的橙色区域)。当然在应用层和网络层也有应用拦截器和网络拦截器。我们主要分析Okhttp自带的拦截器

    那么这些拦截器是负责做什么的呢?

    首先这些拦截器构成一条链(可以理解成链条),这些拦截器会在发起请求前对request进行处理,然后调用下一个拦截器,获取response,最后对response进行处理,返回给上一个拦截器。

    接下来先大致看一下它们的作用,在后面会详细介绍这些拦截器的实现流程

    1.RetryAndFollowUpInterceptor(重试重定向拦截器)

    重试那些失败或者重定向的请求。因为在整个网络请求的过程中,网络可能不稳定

    2.BridgeInterceptor(桥接拦截器)

    请求之前对响应头做了一些检查,并添加一些头。负责将用户构建好的Request请求转换成可以进行网络访问的请求

    3.CacheInterceptor(缓存拦截器)

    做一些缓存的工作。获取响应时先从缓存里去获取,如果缓存没有才会进行网络请求,并把获取到响应后放入缓存

    4.ConnectInterceptor(连接拦截器)

    负责建立和目标服务器的连接

    5.CallServerInterceptor(发送服务请求拦截器)

    负责向服务器发起真正的网络请求

    这些拦截器流程走向如下

    interceptor.jpg

    这些拦截器是什么时候被创建的呢?

    这个时候我们就可以进入同步的请求execute方法

    public Response execute() throws IOException{
     synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        timeout.enter();
        eventListener.callStart(this);
        try {
          client.dispatcher().executed(this);
          //通过拦截器链获取响应
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } catch (IOException e) {
          e = timeoutExit(e);
          eventListener.callFailed(this, e);
          throw e;
        } finally {
          client.dispatcher().finished(this);
        }
     }
    

    通过一个ArrayList集合创建这些拦截器,形成一条链条,互相影响。也可以看成是一个栈,内部是递归的形式一层层返回响应结果。

     Response getResponseWithInterceptorChain() throws IOException {
        //把所有拦截器放入list集合中
        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));
        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);
      }
    

    这里我没有分析异步的过程,因为它比同步复杂多了,不信你看

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

    确定没有逗我???我可是受过九年义务教育的。这代码量比enqueue简短多了。这是因为它把职责交给了Dispatcher类了,而Dispatcher又牵涉到线程池等。

    调用Dispatcher.enqueue时传入了一个AsyncCall对象

    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
        AsyncCall(Callback responseCallback) {
          super("OkHttp %s", redactedUrl());
          this.responseCallback = responseCallback;
        }
        protected void execute() {
         boolean signalledCallback = false;
          timeout.enter();
          try {
            Response response = getResponseWithInterceptorChain(); 
            }
            //...
        }
        
    public abstract class NamedRunnable implements Runnable {
      //...
      public final void run() {
        try {
          execute();  //执行子类的execute方法
        }
        //...
     }
     protected abstract void execute();
    }
    

    可以看到,在异步请求的过程中,它最终还是会调用 getResponseWithInterceptorChain方法,也就是通过拦截器链获取返回的响应。

    到这里我们对拦截器有了一个浅层的认识了,下面开始详细介绍每个拦截器的实现流程

    2.1 RetryAndFollowUpInterceptor

    public final class RetryAndFollowUpInterceptor implements Interceptor {
        
        private volatile StreamAllocation streamAllocation;  //建立执行http请求的所有网络组件
        //......
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Call call = realChain.call();
            EventListener eventListener = realChain.eventListener();
            //1.创建StreamAllocation对象
            StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
             //进行网络请求连接
            while (true) {
                //...
                //2.调用RealInterceptorChain.proceed(),调用下一个拦截器进行网络请求连接,获取response
               try {
                response = realChain.proceed(request, streamAllocation, null, null);
                releaseConnection = false;
              } catch (RouteException e) { }
                
                //...
              try {
                //重定向请求
                followUp = followUpRequest(response, streamAllocation.route());
              } catch (IOException e) {
                streamAllocation.release();
                throw e;
              }
             //超过一定的重连次数,抛出异常
            if (++followUpCount > MAX_FOLLOW_UPS) {
                streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
          }
        }
    }
    
    

    大致流程

    1. 创建StreamAllocation对象,用于建立执行http请求的所有网络组件
    2. 调用RealInterceptorChain.proceed()来调用下一个拦截器,获取响应结果response
    3. 根据响应结果判断是否进行重新请求

    2.2 BridgeInterceptor

    public final class BridgeInterceptor implements Interceptor {
        
        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());
              }
            if (contentLength != -1) {
                requestBuilder.header("Content-Length", Long.toString(contentLength));
                requestBuilder.removeHeader("Transfer-Encoding");
            }
             //....
            }   
    
         responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }
         return responseBuilder.build();
    }
    

    大致流程:

    1. 对请求头做了一些检查,并添加一些头,目的是Request请求转换成可以进行网络访问的请求
    2. 将这个符合网络请求的request进行网络请求
    3. 然后将请求回来的响应结果Response转换为用户可见的Response

    2.3 CacheInterceptor

    CacheInterceptor内部实现了一个缓存策略类CacheStrategy

    public final class CacheInterceptor implements Interceptor {public Response intercept(Chain chain) throws IOException {
        Response cacheCandidate = cache != null? cache.get(chain.request()): null;
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;
        //调用下一个拦截器
          try {
          networkResponse = chain.proceed(networkRequest);
        } finally {
        //...
        }
    }
    

    先判断缓存有没有响应,如果有就从缓存里获取;如果缓存没有就从网络中获取响应

    //如果缓存有响应
         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();
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    

    如果缓存没有响应且网络请求结果为空,抛出504码错误

      //如果缓存没有响应且网络请求结果为空
         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();
        }
    

    大致流程如下:

    1. 获取本地缓存cacheCandidate,如果本地缓存可用则打断interceptor链,返回cacheCandidate,
    2. 调用下一个interceptor获取networkResponse
    3. 用networkResponse、cacheResponse构造新的response
    4. 根据新的response里的header通过缓存策略存入缓存中

    2.4 ConnectInterceptor

    okhttp的一大特点就是通过连接池来减小响应延迟。如果连接池中没有可用的连接,则会与服务器建立连接,并将socket的io封装到HttpStream(发送请求和接收response)中,这些都在ConnectInterceptor中完成。

    public final class ConnectInterceptor implements Interceptor {
        public Response intercept(Chain chain) throws IOException {
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Request request = realChain.request();
            //获取之前的拦截器传过来的 StreamAllocation
            StreamAllocation streamAllocation = realChain.streamAllocation();
        
            // We need the network to satisfy this request. Possibly for validating a conditional GET.
            boolean doExtensiveHealthChecks = !request.method().equals("GET");
            HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
             //通过streamAllocation获取RealConnection对象
            RealConnection connection = streamAllocation.connection();
        
            return realChain.proceed(request, streamAllocation, httpCodec, connection);
          }
    }
    

    大致流程:

    1. ConnectInterceptor获取之前的拦截器传过来的 StreamAllocation
    2. 通过streamAllocation获取RealConnection对象,它是真正用于连接网络的对象
    3. 将RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截器

    HttpCodec的作用简单的理解就是对request编码,对response解码

    2.5 CallServerInterceptor

    CallServerInterceptor是最后一个拦截器,它负责发起真正的网络请求并接收服务器返回的响应数据

    public final class CallServerInterceptor implements Interceptor {
        public Response intercept(Chain chain) throws IOException {
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            HttpCodec httpCodec = realChain.httpStream();
            StreamAllocation streamAllocation = realChain.streamAllocation();
            RealConnection connection = (RealConnection) realChain.connection();
            Request request = realChain.request();
            
            realChain.eventListener().requestHeadersStart(realChain.call());
            //写入请求头信息
            httpCodec.writeRequestHeaders(request);
            realChain.eventListener().requestHeadersEnd(realChain.call(), request);
            //写入请求体的信息
            request.body().writeTo(bufferedRequestBody);
            //完成网络请求工作
            httpCodec.finishRequest();
            if(responseBuilder == null) {
                  realChain.eventListener().responseHeadersStart(realChain.call());
                  responseBuilder = httpCodec.readResponseHeaders(false);
            }     
        
           //读取网络响应的头信息
           if (responseBuilder == null) {
              realChain.eventListener().responseHeadersStart(realChain.call());
              responseBuilder = httpCodec.readResponseHeaders(false);
           }
           //读取网络响应的body信息
           if (forWebSocket && code == 101) {
                // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
              response = response.newBuilder()
                  .body(Util.EMPTY_RESPONSE)
                  .build();
            } else {
              response = response.newBuilder()
                  .body(httpCodec.openResponseBody(response))
                  .build();
            }
        }
       return response;
    }
    

    大致流程:

    1. 写入请求头和请求体信息,发起正在的网络请求
    2. 获取网络请求返回的响应,读取响应头和响应体的信息
    3. 返回最终的响应结果

    三、总结

    一图胜千言

    同步和异步请求.jpg

    到这里把OkHttp的执行流程都介绍了一遍,但是具体到每一个部分内部实现原理远没有我说的这么简单,牵涉到很多的知识点。这里就不进行分析了,一篇文章也是不能讲清楚的。相信你看完这篇文章之后,再去看它的Okhttp的源码,会更轻松的。

    相关文章

      网友评论

        本文标题:一篇文章带你熟悉OkHttp

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