ok?->okhttp

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

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大神的图和总结)

瞅你咋地?
  • OkHttpClient 实现 Call.Factory,负责为 Request 创建 Call;
  • RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用 ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过 getResponseWithInterceptorChain() 函数实现;
  • getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容