Okhttp网络框架的基本使用及源码解析

版权声明:欢迎来到我的个人博客:http://blog.N0tExpectErr0r.cn https://blog.csdn.net/qq_21556263/article/details/82768320

Okhttp网络框架的学习及源码分析

HTTP及相关协议

HTTP协议

出现及发展

  • 1960年美国人Ted Nelson提出想法
  • 由万维网协会(www)和互联网工程工作共同研究
  • 目前使用最广泛的是HTTP1.1

HTTP协议结构

HTTP协议的结构有四个主要组成部分:

  • 请求头
    • 表明本次请求的客户端
    • 本次请求所使用的cookie(标明本次请求的用户身份)
    • 本次请求希望返回的数据类型(json、string…)
    • 本次请求是否采用数据压缩等一系列设置
  • 请求体
    • 指定本次请求使用的方法
    • 携带本次请求参数
  • 响应头
    • 服务器标识
    • 状态码
    • 内容编码
    • cookie(返回给客户端用于下次请求使用)
  • 响应体
    • 主要是我们本次请求所返回的数据

HTTP工作流程

  • 首先,客户机与服务器要建立连接
  • 建立连接后,客户机发送请求给服务器
  • 服务器接收到请求后,给予响应的响应
  • 客户端接收服务器返回的信息,断开连接

优势

  • 简单、快速
  • 灵活
  • 无连接
  • 无状态

SPDY协议

在早期HTTP中,由于互联网的协议都比较简单,基本都是一些静态的页面。因此无连接无状态的特点可以使简单、快速。灵活的优势发挥到最大。但随着业务逻辑的复杂、安全性要求提高等等,无连接无状态正在成为HTTP的劣势。因此才会出现一些更加高级的协议,如SPDY。

SPDY是HTTP的兼容协议,基于HTTP。除了HTTP的几个最基本的功能外,还有以下的优点:

  • 多路复用请求
  • 对请求划分优先级
  • 压缩Http头

HTTP2.0协议

HTTP2.0协议是完全基于SPDY,IETF定制的新一代HTTP协议。它相比SPDY有着更安全的SSL协议,采用了更安全的加密算法等等…

okhttp网络框架

简介

既然我们的协议已经支持了HTTP2.0,但我们的客户端该如何使用HTTP2.0呢?

okhttp框架为Android提供了解决方案。它由著名的square团队开发,并且开源。这里是它的github地址

优势

  • 支持SPDY,HTTP2.0,HTTP1.0及HTTP1.1
  • SPDY,HTTP2.0共享同一个Socket来处理同一个服务器的所有请求,来提高连接的效率
  • 如果SPDY不支持,则通过连接池来减少请求的延时
  • 缓存相应数据来减少重复的网络请求
  • 可以从很多常见连接问题中自动恢复
  • 使用简单

使用方法

GET

下面我们来看一下okhttp的使用方法。

首先,我们需要通过Request的Builder来为它设置url等参数。

 Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();

之后,我们需要通过OkHttpCilent来构建一个Call,需要将Request传入。

然后我们可调用Call的enqueue()方法,并传入一个Callback,来响应我们的请求。

OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
       	//do sth
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        final String msg = response.body().string();
        //do sth
    }
});

这样就成功发送了一个GET请求

POST

要发送POST请求,首先仍然是构建我们的Request。这次需要用到FormBody来添加键值对参数。再在构建Request时用post方法来将FormBody传入。

FormBody.Builder formBodyBuild = new Builder();
formBodyBuild.add("username","USERNAME")
        .add("password","PASSWORD");
Request request = new Request.Builder().url(LOGIN_URL).post(formBodyBuild.build()).build();

然后我们就可以像之前那样构建一个Call:

OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
       	//do sth
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        final String msg = response.body().string();
        //do sth
    }
});

这样就达到了发送POST请求的效果。z

原理解析

总体设计

okhttp的总体设计图如下,首先通过构建者模式构建Request。(okhttp中几乎所有的类都用到了构建者模式)之后将request分发到Dispatcher中,然后再分发到HttpEngine中。

HttpEngine首先查看本次请求是否有缓存(Cache)。如果有,就从Cache中拿到信息直接给Response。如果没有,则会把这次请求发到ConnectionPool中,从连接池中获取到Connection以后,通过Connection去发送真正的请求。请求到之后,又通过Route和Platform找到一个合适的平台。最后通过Server Socket去获取Data。

img

流程图

以下是okhttp的流程图。由于我们常用异步请求,因此从异步请求的分支来讲解。

首先要创建请求Call,然后发起异步请求。异步请求在发起之前会通过一系列的拦截,拦截一些无效或恶意的请求。之后到达HttpEngine中,在有缓存的情况下通过缓存文件系统来获取Response。没有缓存则真正的去发送网络请求获取Response。之后相应Request

img

核心类图

  • Dispatcher是分发器,主要用于分发异步任务。
  • Protocol则是列举了okhttp支持的协议(SPDY,HTTP2.0,HTTP1.0及HTTP1.1)的枚举
  • ConnectionSpec用于指定每个Connection中的一些常量和变量等。
  • ConnectionPool就是用于管理Connection的连接池。内部维护了一个列表,这个列表就是本系统内所有可用的Connection。
  • Interceptor是过滤器,由过滤器构成了Interceptor.Chain,也就是过滤器列。
  • InternalCache和Cache共同构成了okhttp的缓存系统。

这些子类都是为OkhttpClient类服务的。OkhttpClient几乎引用了所有的类,是整个okhttp框架的核心类。它调用对应的模块去达到发送请求、相应请求等操作。

img

okhttp的多路复用机制

首先,HttpEngine类在发送请求前会调用nextConnection方法,来获取Connection对象。如果能从ConnectionPool中获取到Connection对象,则不会新建一个Connection。如果获取不到,则调用createNextConnection方法去创建Connection。

下面是连接执行的时序图:

img

okhttp的重连机制

Call发送到HttpEngine中时,每次都会判断是否能getResponse()。如果不可以,则会执行recover()方法,开始Retry()机制

下面是自动重连机制的时序图:

img

源码分析

Route类

Route就是一个路由类。它封装了一个Address类,里面就包含了所有请求的信息,如端口号、服务器ip地址等等。最后要建立Connection时会从Address中获取相应数据。

public final class Route {
  final Address address;
  final Proxy proxy;
  final InetSocketAddress inetSocketAddress;
  ...
}

Address类

public final class Address {
  final HttpUrl url;
  final Dns dns;
  final SocketFactory socketFactory;
  final Authenticator proxyAuthenticator;
  final List<Protocol> protocols;
  final List<ConnectionSpec> connectionSpecs;
  final ProxySelector proxySelector;
  final Proxy proxy;
  final SSLSocketFactory sslSocketFactory;
  final HostnameVerifier hostnameVerifier;
  final CertificatePinner certificatePinner;
  ...
}

ResponseBody类

从名字就可以看出,它就是一个响应体类。对应了响应中的响应体。它有一个最核心的方法,就是bytes()方法。因为我们返回的所有数据,它最初返回的样子都是我们的字节码。可以通过responseBody,去获取对应的数据。

 public final byte[] bytes() throws IOException {
   long contentLength = contentLength();
   if (contentLength > Integer.MAX_VALUE) {
     throw new IOException("Cannot buffer entire body for content length: " + contentLength);
   }
   BufferedSource source = source();
   byte[] bytes;
   try {
     bytes = source.readByteArray();
   } finally {
     Util.closeQuietly(source);
   }
   if (contentLength != -1 && contentLength != bytes.length) {
     throw new IOException("Content-Length and stream length disagree");
   }
   return bytes;
 }

比如如果要获取String类型的数据,可以通过byte数组转化成String。如果想获取图片,也是一样的道理。

Response类

Response就是我们的响应。它包含了响应头Headers与响应体responseBody,它们也是Response最主要的两个成员变量。它内部是一个构建者模式,来构建Response。

public final class Response {
  private final Request request;
  private final Protocol protocol;
  private final int code;
  private final String message;
  private final Handshake handshake;
  private final Headers headers;
  private final ResponseBody body;
  private Response networkResponse;
  private Response cacheResponse;
  private final Response priorResponse;
  ...
}

RequestBody类

显而易见,RequestBody对应的就是我们的请求体了,它其实是一个抽象类,定义了一些抽象的方法。它有两个实现的类,分别是FormBody以及MultipartBody。

FormBody

FormBody是RequestBody的其中一个实现类,在发送Post请求时,会构建一个FormBody,将键值对分别传入。是以构建者方法来创建的。

public final class FormBody extends RequestBody {
  private static final MediaType CONTENT_TYPE =
      MediaType.parse("application/x-www-form-urlencoded");

  private final List<String> encodedNames;
  private final List<String> encodedValues;
  ...
}
MultipartBody

MultipartBody主要用于上传文件时使用,因为FormBody只能上传key,value这样简单类型的数据。而MultipartBody就可以上传对象类型的数据。它也是用构建者模式来创造,内部有个关键的方法,在Builder中的addFormDataPart方法。它有几种重载,一种可以简单地以key value的方式添加,另一种则可以通过RequestBody来上传文件等。

Request类

Request也是一个构建者模式,它内部封装了本次请求的URL,请求要用的method,以及请求头和请求体。而tag,则是用于取消请求。

public final class Request {
  private final HttpUrl url;
  private final String method;
  private final Headers headers;
  private final RequestBody body;
  private final Object tag;
  ...
}

Call接口

这里的Call是一个接口,它代表了一个任务,就是真正需要HttpEngine去执行的任务。实现这个接口的则是RealCall类

RealCall类

RealCall是一个Runnable,实现了Call接口。它真正去执行了对请求的处理。比如可以通过它的getResponse方法拿到我们请求中的数据。然后通过HttpEngine去发送请求。

Response getResponse(Request request, boolean forWebSocket) throws IOException {
	...
	RequestBody body = request.body();
	...
	request = requestBuilder.build();
	...	
	engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null)
	...
}

HttpUrl类

HttpUrl是一个HTTP的Url工具类,它封装了一系列的对url处理的方法。

Headers类

Headers类代表了请求和响应的头。它内部有个很巧妙的设计,它也是键值对存放,但并没有用两个列表或者一个HashMap,而是通过一个String类型的数组,就实现了存放一个二维的key value。

public final class Headers {
  private final String[] namesAndValues;
  ...
}

ConnectionPool类

ConnectionPool是okhttp中设计的连接池,是通过SynchronousQueue这种队列来实现的。在连接池中包含了各种管理连接的方法。

Callback类

Callback就是我们的相应的回调。最终就是要继承它来完成一些获得响应后的处理等等。里面有两个方法onFailure与onResponse。

Dispatcher类

Dispatcher类在okhttp起到分发器的作用。它内部有一个ExecutorService线程池,以及三种请求的队列。分别是一个异步准备就绪的任务队列,一个异步的正在执行的任务队列,以及一个同步的任务队列。

它内部最核心的方法就是executed方法,里面传入了RealCall参数。会将RealCall添加到正在执行的队列中。然后依次执行RealCall。RealCall又会调用HttpEngine来构建Connection,发送请求等,最后获取到一个最终的Response。最终的Response封装在了ResponseBody中。最后通过Callback的onResponse方法拿到Response,进行相应操作。

HttpEngine类

HttpEngine从字面意思上来看,就是okhttp的引擎了。它是真正需要发送请求、负责请求的重连机制的类。

sendRequest方法

首先看它的sendRequest方法。它先通过networkRequest方法去创建对应的Request。有了这个Request方法后,在缓存策略中查看是否有这样的缓存。如果有,则直接返回。如果没有,则真正的去构建一个请求,直到相应后获得相应的Response。

public void sendRequest() throws RequestException, RouteException, IOException {
  if (cacheStrategy != null) return; // Already sent.
  if (httpStream != null) throw new IllegalStateException();
  Request request = networkRequest(userRequest);
  InternalCache responseCache = Internal.instance.internalCache(client);
  Response cacheCandidate = responseCache != null
      ? responseCache.get(request)
      : null;
  long now = System.currentTimeMillis();
  cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  networkRequest = cacheStrategy.networkRequest;
  cacheResponse = cacheStrategy.cacheResponse;
  if (responseCache != null) {
    responseCache.trackResponse(cacheStrategy);
  }
  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }
  // If we're forbidden from using the network and the cache is insufficient, fail.
  if (networkRequest == null && cacheResponse == null) {
    userResponse = new Response.Builder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .protocol(Protocol.HTTP_1_1)
        .code(504)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(EMPTY_BODY)
        .build();
    return;
  }
  // If we don't need the network, we're done.
  if (networkRequest == null) {
    userResponse = cacheResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .build();
    userResponse = unzip(userResponse);
    return;
  }
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean success = false;
  try {
    httpStream = connect();
    httpStream.setHttpEngine(this);
    if (writeRequestHeadersEagerly()) {
      long contentLength = OkHeaders.contentLength(request);
      if (bufferRequestBody) {
        if (contentLength > Integer.MAX_VALUE) {
          throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
              + "setChunkedStreamingMode() for requests larger than 2 GiB.");
        }
        if (contentLength != -1) {
          // Buffer a request body of a known length.
          httpStream.writeRequestHeaders(networkRequest);
          requestBodyOut = new RetryableSink((int) contentLength);
        } else {
          // Buffer a request body of an unknown length. Don't write request headers until the
          // entire body is ready; otherwise we can't set the Content-Length header correctly.
          requestBodyOut = new RetryableSink();
        }
      } else {
        httpStream.writeRequestHeaders(networkRequest);
        requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
      }
    }
    success = true;
  } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (!success && cacheCandidate != null) {
      closeQuietly(cacheCandidate.body());
    }
  }
}
recover方法

HttpEngine中,recover的作用就是重连的方法。在它内部进行的操作就是重置一些参数,再去发送请求。

public HttpEngine recover(IOException e, Sink requestBodyOut) {
  if (!streamAllocation.recover(e, requestBodyOut)) {
    return null;
  }
  if (!client.retryOnConnectionFailure()) {
    return null;
  }
  StreamAllocation streamAllocation = close();
  // For failure recovery, use the same route selector with a new connection.
  return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
      forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
}

OkHttpCilent类

OkHttpCilent是okhttp的核心类,它里面几乎持有了所有的类的引用。它也是一个构建者类来进行构建的。它的作用就是调用各个模块而达到okhttp的功能的实现。


广告时间
我是N0tExpectErr0r,一名广东工业大学的大二学生
欢迎来到我的个人博客,所有文章均在个人博客中同步更新哦
http://blog.N0tExpectErr0r.cn

猜你喜欢

转载自blog.csdn.net/qq_21556263/article/details/82768320