OkHttp库源码解析

OkHttp库使用及源码解析

特点

  • OkHttp是一个高效的Http客户端,有如下的特点:
  • 支持HTTP2/SPDY黑科技
  • socket自动选择最好路线,并支持自动重连
  • 拥有自动维护的socket连接池,减少握手次数
  • 拥有队列线程池,轻松写并发
  • 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  • 基于Headers的缓存策略

使用

OkHttp是由Square开发的网络通信库,使用前先添加依赖库

dependencies {
    ...
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
}

不要忘记声明使用网络的权限

<uses-permission android:name="android.permission.INTERNET" />

一个简单的GET请求的步骤(阻塞方式):

  • 创建一个OkHttpClient对象
  • 创建一个Request对象
  • 调用client.newCall(request)创建Call对象
  • 调用call.execute()执行请求(阻塞)
try
                {
                    OkHttpClient client = new OkHttpClient();
                    Request request = new Request.Builder() //这里创建了Builder对象,然后进行配置
                            .url("http://www.baidu.com")
                            .build(); //创建Request对象
                    Response response = client.newCall(request).execute(); //创建一个Call对象,然后发送请求,response接收返回的数据,此方法blocks直到响应返回
                    String message = response.message();    //response状态
                    String responseData = response.body().string(); //将返回的数据转换为字符串
                    Log.d(TAG, "message : " + message);
                    Log.d(TAG, "data: \n" + responseData);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }

当请求的域名不存在时会抛出异常:java.net.UnknownHostException

更好的用法(异步)

来自CSDN博客

//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建一个Request
final Request request = new Request.Builder()
                .url("https://github.com/hongyangAndroid")
                .build();
//new call
Call call = mOkHttpClient.newCall(request); 
//请求加入调度
call.enqueue(new Callback()
        {
            @Override
            public void onFailure(Request request, IOException e)
            {
                //这里处理请求时发生的异常
            }

            @Override
            public void onResponse(final Response response) throws IOException
            {
                //这里处理返回的response
            }
        }); 

调用enqueue()方法使得call的执行是异步的,不影响其他代码的执行。
当请求执行完后,会回调Callback里的方法,我们可以在里面执行我们的代码。

查看源码可以知道onFailure调用的时机:

Called when the request could not be executed due to cancellation, a connectivity problem or timeout.
当request由于取消、连接问题或者超时导致不能完成的情况下调用。

提交数据的POST请求

代码如下,其它跟上面的一样

RequestBody requestBody = new FormBody.Builder()
                            .add("username", "admin")
                            .add("password", "123456")
                            .build();

                    Request request = new Request.Builder() //这里创建了Builder对象,然后进行配置
                            .url("http://www.baidu.com")
                            .post(requestBody)
                            .build(); //创建Request对象

Builder的post()方法

public Builder post(RequestBody body) {
      return method("POST", body);
    }

当Builder调用了post(RequestBody)方法后自动将请求方法设为“POST”

源码解析

OkHttp中主要的几个类:
- RealCall,Call的实现类,读源码从这里入手
- RealConnection,Connection的实现类,主要做TCP连接和协议(TSL握手)处理
- HttpCodec,编码Http请求和解码Http响应
- OkHttpClient,做配置工作
- Dispatcher,做异步工作的分发
- RealInterceptorChain,拦截链,负责取出拦截器并执行

OkHttp的线程池

//最大同时请求数
  private int maxRequests = 64;
  //每个IP最多请求数
  private int maxRequestsPerHost = 5;
  synchronized void enqueue(AsyncCall call) {
    //从判断条件可以看出,OKHttp3同时最多有64个请求执行,而每一个host最多有5个请求同时执行。其他请求都放在等待队列中。
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //全部使用非核心线程,60秒超时回收
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

对于同步Call和异步Call的处理

使用Call.execute:

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

使用Call.enqueue:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

同步Call是直接在调用方所在线程执行网络请求操作,而异步Call需要提交到线程池中执行。

无论是同步还是异步Call,最终都识调用RealCall中的getResponseWithInterceptorChain()方法来获得响应:

private 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));
    if (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
public final class RealInterceptorChain implements Interceptor.Chain {
    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);
}

我们手动添加的拦截器是处于最上层的,而最下层的是CallServerIntercepter负责真正的网络请求处理。

拦截器

OkHttp用责任链设计模式来将Request层层转交给下面的拦截器进行处理,每一层都可以拿到上一层的request,而且修改返回的Response,转交的顺序是:

  1. 用户设置的client.interceptors
  2. retryAndFollowIntercepter //重试和重定向处理拦截器
  3. BridgeIntercepter //将应用代码转成网络代码,包括cookie的设置、user-agent等
  4. CacheIntercepter //处理缓存
  5. ConnectIntercepter //创建web连接
  6. 非WebSocket下:用户设置的networkIntercepters
  7. CallServerIntercepter //往TCP流中写request,读response

献上一张神图来解释这条链是怎么运作的:
OkHttp责任链

HttpStream和StreamAllocation

HttpStream

HttpStream主要定义了写请求头、写请求体和读响应头、读响应体的接口方法。

public interface HttpStream {
  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;

  Sink createRequestBody(Request request, long contentLength);

  void writeRequestHeaders(Request request) throws IOException;

  void finishRequest() throws IOException;

  Response.Builder readResponseHeaders() throws IOException;

  ResponseBody openResponseBody(Response response) throws IOException;

  void cancel();
}

HttpStream的实现类有:Http1xStream、Http2xStream,分别对应HTTP/1.1和HTTP/2。

HttpStream的实现类使用source来写入请求体,使用sink来读取响应体,这两个都是缓存流。

public final class Http1xStream implements HttpStream {
    private final BufferedSource source;
    private final BufferedSink sink;
}

StreamAllocation
定义了三个实体:
1. Connections 物理的socket连接
2. Streams 逻辑的Http请求/响应体,一个Connection可以有多个并发的Streams,但是有一个分配限制(allocation limit)
3. Calls streams的序列,通常是一个初始的request和后跟的request

Instances of this class act on behalf of the call, using one or more streams over one or more connections。

public final class StreamAllocation {
  public final Address address;
  private Route route;
  private final ConnectionPool connectionPool;  连接池

  // State guarded by connectionPool.
  private final RouteSelector routeSelector;
  private int refusedStreamCount;
  private RealConnection connection;  当前连接
  private boolean released; 连接池是否被释放了
  private boolean canceled; 连接取消
  private HttpStream stream;

}

BridgeInterceptor

主要是将处理请求的header和body的规范化(用用户代码到网络代码),包括Gzip编码和解码:

public final class BridgeInterceptor implements Interceptor {
  private final CookieJar cookieJar;

@Override 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"); //默认keep-Alive,即不断开TCP连接
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false; //如果没有主动设置Accept-Encoding,默认是gzip,同时我们还负责将response解压缩
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    //如果设置了CookieJar,添加Cookie
    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()); //默认是"okhttp/3.11.0"
    }

    //交给下一拦截器处理,获取响应
    Response networkResponse = chain.proceed(requestBuilder.build());
    //拿到响应头
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    //对gzip的响应包进行处理
    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();
  }
}

默认的CookieJar,不进行任何保存:

public interface CookieJar {
  /** A cookie jar that never accepts any cookies. */
  CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };
}

读取响应头,保存Cookie:

public final class HttpHeaders {
  public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    if (cookieJar == CookieJar.NO_COOKIES) return;

    List<Cookie> cookies = Cookie.parseAll(url, headers);
    if (cookies.isEmpty()) return;

    //调用CookieJar的回调方法保存
    cookieJar.saveFromResponse(url, cookies);
  }
}

Cookie.parseAll就是通过响应头的“Set-Cookie”值获取:

  /** Returns all of the cookies from a set of HTTP response headers. */
  public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
    List<String> cookieStrings = headers.values("Set-Cookie");
    List<Cookie> cookies = null;

    for (int i = 0, size = cookieStrings.size(); i < size; i++) {
      Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
      if (cookie == null) continue;
      if (cookies == null) cookies = new ArrayList<>();
      cookies.add(cookie);
    }

    return cookies != null
        ? Collections.unmodifiableList(cookies)
        : Collections.<Cookie>emptyList();
  }

ConnectInterceptor

做TCP连接的拦截器:从连接池中获取Connection,如果没有就创建一个接:

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

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

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //查找RealConnection并返回一个String
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

CallServerInterceptor

真正进行网络请求的拦截器,把resquest发送到TCP流上,把Response从TCP读进来,主要流程如下:

public final class CallServerInterceptor implements Interceptor {
  private final boolean forWebSocket;

  public CallServerInterceptor(boolean forWebSocket) {
    this.forWebSocket = forWebSocket;
  }
@Override public Response intercept(Chain chain) throws IOException {
    ...
    //1.写请求头
    httpStream.writeRequestHeaders(request);

    //这里判断请求方法,GET方法不能发送请求体
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      //2.写请求体
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    }

    //提交(提交buffer的内容)
    httpStream.finishRequest();

    //3.读响应头
    Response response = httpStream.readResponseHeaders()  
        .request(request)
        .handshake(streamAllocation.connection().handshake())   //TSL握手
        .sentRequestAtMillis(sentRequestMillis) //发送时间
        .receivedResponseAtMillis(System.currentTimeMillis()) //接收时间
        .build();

    //4.如果当前不是websocket的话,读响应体
    if (!forWebSocket || response.code() != 101) {
      response = response.newBuilder()
          .body(httpStream.openResponseBody(response))
          .build();
    }

    //遇到响应体有Connection:close时释放连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();  
    }

    //响应码为204、205的没有响应体,而且不进行页面跳转
    int code = response.code();
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
}

OkHTTP连接池

public final class ConnectionPool {
    //默认闲置连接数:5,连接keep alive:5分钟
    public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }
}

OkHTTP的缓存实现

OkHttp在责任链中添加了一个CacheIntercepter,主要用来从缓存中读取和保存response。

CacheInterceptor

缓存拦截器,在Brige层下面:

public final class CacheInterceptor implements Interceptor {
  final InternalCache cache;

  public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    ...
    }
}

可以看到缓存是通过InternalCache来完成的。从OkHttp的构建来看,默认的Cache和InternalCache是null。我们再往下看:

    long now = System.currentTimeMillis();

    //这里涉及到缓存策略的问题,提供一个请求和一个已存在的缓存,让缓存策略判断是否使用这个缓存。
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    ...
    // 如果我们不允许使用网络(比如设置了CacheControl.FORCE_CACHE),而且缓存也不可用,则返回一个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();
    }

    // If we don't need the network, we're done. 使用缓存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    ···
    //下面的就是不使用缓存,进行网络请求
    networkResponse = chain.proceed(networkRequest);

    //下面处理的一个情况是服务器返回304 NOT MODIFIED,表示服务器这边的数据没有发生变化,可以使用缓存。
    // 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();
        cache.update(cacheResponse, response); //保存更新后的缓存

        //可以返回response了
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    //下面就是没有用到缓存的情况,获得了正常的response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //根据缓存策略,如果需要缓存则保存起来
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      //如果本次请求是POST、PATCH、PUT、DELETE、MOVE方法的话,则删除缓存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

猜你喜欢

转载自blog.csdn.net/mingC0758/article/details/81592906