一篇文章带你熟悉OkHttp

2 篇文章 0 订阅
1 篇文章 0 订阅

一、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的源码,会更轻松的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值