NetAndroid开发经验谈Android知识

ok?->okhttp

2017-09-26  本文已影响33人  乐之飞于

首先,阅读完本文希望你能回答:

ok(指的okhttp)它是怎么发起(创建和建立)http请求的呢?

ok它是怎么创建call的呢?

ok里面HttpCodec对象是啥?

ok它是怎么进行和服务器实际通信的?

ok它是怎么样实现同步网络和一部网络请求的?代码的结构和设计模式方面你得到了什么么?

废话不多说,ok它提供okhttpclient()

public OkHttpClient() { this(new Builder()); }

方便我们使用,提供快捷操作,全部使用默认配置。OkHttpClient.Builder类成员很多,这里略过:

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
}

它是怎么发起http请求的呢?(涉及到创建和建立)

看里面的run()方法

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

okhttpclient 实现了call.factory,负责根据请求创建新的call。
callFactory 负责创建 HTTP 请求,HTTP 请求被抽象为了 okhttp3.Call 类,它表示一个已经准备好,可以随时执行的 HTTP 请求

它是怎么创建call的呢?


/\*\* \* Prepares the {@code request} to be executed at some point in the future. \*/
 @Override public Call newCall(Request request) {
                  return new RealCall(this, request); 
}

A.发现跟源码,出现了realcall这个类。接下来了解下它的同时,分析下同步网络请求的过程

realcall里面的 excute方法:

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

相信大家也看到了1,2,3,4了不卖关子,这里做的4件事情是在干什么呢?

1.检查:查call是否已经执行,每个call只能被执行一次,如果想要一个一摸一样的call,也提供了clone的方法。

2.执行:dispatcher是 上面说的OkHttpClient.Builder成员之一。

看源码的时候这个类说自己是异步http请求的执行策略,现在再看,同步tm也掺合进来了

3.结果:调用这个函数获取htto的返回结果,函数名称上来看,这一步还会进行一系列的“拦截”操作(getResponseWithInterceptorChain())

4.over:通知dispatcher自己执行完成

说了上面四点,又发现什么么?

 Policy on when async requests are executed. Each dispatcher uses an {@link ExecutorService} 
to run calls internally. If you supply your  own executor,
 it should be able to run {@linkplain getMaxRequests the configured maximum} 
number  of calls concurrently. 

dispatcher注释(ok的任务队列的管理与调度其实就是Dispatcher一个类来完成,虽然我们只是针对异步任务来讲解,但是它也负责同步任务的维护,如 executed()方法的调用标识任务的开始,finished()方法的调用标识任务结束,具体代码可以自行查阅。在任务被调度执行以后,任务就需要去执行了,也就是请求流程的执行过程。本篇文章的最后一部分对此做介绍,这一部分也是很多文章都会重点介绍的interceptor的调用流程,在okhttp中所有的功能几乎都是通过定义interceptor, 对request和response做操作来实现的,其实就也就是请求流程的执行过程。)

真正发出网络请求的,解析返回结果的还是getResponseWithInterceptorChain:

先看图:


你瞅啥?

一个接力传递,为了描述涉及到两个类的递归过程

 A concrete interceptor chain that carries the entire interceptor chain:
 all application \* interceptors, the OkHttp core, all network interceptors, 
and finally the network caller. 

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

之前看到okhttp开发者之一的作者有写到:the whole thing is just a stack of built-in interceptors.

可见拦截器是okhttp最核心的一个,不要误以为它只负责拦截请求进行了一些额外的处理(如cookie),实质上它吧网络请求,缓存,透明压缩等功能都同意了起来。每一个功能都是一个拦截器Interceptor,它们再连接一个chain。最后才完成一次网络请求

从getResponseWithInterceptorChain函数可以看到,Interceptor.Chain 的分布依次是:

你瞅啥?

这里用网上的一个截图来表示流程。

在配置okhttpclient时设置的interceptors

负责失败重回 i以及重定向的retryandfollowupinterceptor

负责吧用户构造请求转发送到服务器请求,吧度武器返回的响应转换成用户友好的响应的bridgerintecptor

负责读取缓存直接返回,更新缓存的cache;和服务器建立连接的connect

配置okhttpclient时设置的network;负责向服务器发送请求数据,从服务器读取响应数据的callserver

这个让我看到了一个责任链模式!不得不说okhttp写出来真的花费了很多构思和心血!

它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。--stay

责任链模式在这个 Interceptor 链条中得到了很好的实践!!!学习了

接下来,还有更好的!这个忍不住要说“优雅”,“华丽”!我要说的就是对于request变成response这件事,每个拦截器都有可能完成这件事的流程框架设计。(这个链条让让每个拦截器自己hi还是传递给下一个拦截器去hi很灵活啊!)

这样一来,完成网络的请求彻底从realcall这个类中隔离开了,简化了它们各自的职能。

其实,view的事件分发中对touchevent事件的处理就是比较典型的责任链!

好了接下来带着问题:OkHttp 是怎么进行和服务器实际通信的?

简单的看下建立连接的connectinterceptor和callserverinteceptor

Connectinterceptor
@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();//连接与流的桥梁, 它负责为一次请求寻找连接并建立流,从而完成远程通信,所以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, doExtensiveHealthChecks);//新建流
  RealConnection connection = streamAllocation.connection();//建立连接

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

建立连接其实就是创建HttpCodec 对象,它将在后面被拿出来使用,那它又是啥呢?是

/** Encodes HTTP requests and decodes HTTP responses. */

有两个实现:Http1Codec 和 Http2Codec,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

在 Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装,(Okio它对 java.io 和 java.nio 进行了封装,更便捷高效的进行 IO 操作。)

而创建 HttpCodec 对象的过程涉及到 StreamAllocation、RealConnection,代码较长,这里就不详述,这个过程概括来说,就是:找到一个可用的 RealConnection,再利用 RealConnection 的输入输出(BufferedSource 和 BufferedSink)创建 HttpCodec 对象,供后续步骤使用。

Callserverinteceptor

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

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);    //1. 向socket中写入请求header信息

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        //2. 向socket中写入请求body信息
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }
  httpCodec.finishRequest();    //3. 完成网络请求的写入
    //4. 读取网络响应header信息
  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    //5由 HttpCodec#openResponseBody 提供具体 HTTP 协议版本的响应 body,而 HttpCodec 则是利用 Okio实现具体的数据 IO 操作。
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  // 省略部分检查代码

  return response;
}

可以看出核基本上都时由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio (https://github.com/square/okio)实际上还是用的 Socket。

B.接下来发起异步请求:

client.newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {
  }

  @Override
  public void onResponse(Call call, Response response) throws IOException {
  System.out.println(response.body().string());
  //响应 body 被封装到 ResponseBody 类中,该类主要有两点需要注意:每个 body 只能被消费一次,多次消费会抛出异常;body 必须被关闭,否则会发生资源泄漏;

  }
});

// RealCall#enqueue
@Override
public void enqueue(Callback responseCallback) {
  synchronized (this) {
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
  }
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  runningAsyncCalls.add(call);
  executorService().execute(call);
  } else {
  readyAsyncCalls.add(call);
  }
}

可以发现dispatcher异步的时候,如果还能执行一个并发请求就执行,否则加入到对列,而正在执行的请求执行完就回调用promotecalls函数,来吧对列中的asynccall变为runningasynccall,并且开始执行!(这里的 AsyncCall 是 RealCall 的一个内部类,它实现了 Runnable,所以可以在 ExecutorService 上执行,而它在执行时会调用 getResponseWithInterceptorChain() 函数,并把结果通过 responseCallback 传递给上层使用者。)

无论同步请求和异步请求的原理是一样的,都是在 getResponseWithInterceptorChain() 函数中通过 Interceptor 链条来实现的网络请求逻辑,只不过异步则是通过 ExecutorService 实现。

C.缓存策略方面这里也不过多介绍了,

(主要涉及 HTTP 协议缓存细节的实现,而具体的缓存逻辑 OkHttp 内置封装了一个 Cache 类,它利用 DiskLruCache,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰)

我们可以在构造 OkHttpClient 时设置 Cache 对象,在其构造函数中我们可以指定目录和缓存大小:

public Cache(File directory, long maxSize);

自定义的缓存策略:也可以自行实现 InternalCache 接口,在构造 OkHttpClient 时进行设置。

D.对整体有了清晰认识之后,细节部分如有需要,再单独深入将更加容易。(这里借鉴piasy大神的图和总结)

瞅你咋地?
上一篇下一篇

猜你喜欢

热点阅读