博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析okHttp3的网络请求流程
阅读量:6625 次
发布时间:2019-06-25

本文共 13014 字,大约阅读时间需要 43 分钟。

写在开头

okHttp目前可以称的上是Android主流网络框架,甚至连谷歌官方也将网络请求的实现替换成okHttp.

网上也有很多人对okHttp的源码进行了分析,不过基于每个人的分析思路都不尽相同,读者看起来的收获也各不相同,所以我还是整理了下思路,写了点自己的分析感悟。

本文基于okhttp3.11.0版本分析

基本用法

String url = "http://www.baidu.com";//'1. 生成OkHttpClient实例对象'OkHttpClient okHttpClient = new OkHttpClient();//'2. 生成Request对象'Request request = new Request.Builder()    .url(url)    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))    .build();Call call = okHttpClient.newCall(request);call.enqueue(new Callback() {    @Override    public void onFailure(@NonNull Call call, @NonNull IOException e) {    }    @Override    public void onResponse(@NonNull Call call, @NonNull Response response)  {    }});复制代码

整体流程

借用别人的一张流程图来概括一下okHttp的请求走向

okHttp的整体流程大致分为以下几个阶段
  1. 创建请求对象 (url, method,body)-->request-->Call

  2. 请求事件队列,线程池分发 enqueue-->Runnable-->ThreadPoolExecutor

  3. 递归Interceptor拦截器,发送请求。 InterceptorChain

  4. 请求回调,数据解析。 Respose-->(code,message,requestBody)

创建请求对象

其中 Request维护请求对象的属性

public final class Request {    final HttpUrl url;      final String method;    final Headers headers;    final @Nullable RequestBody body;    //请求的标记,在okHttp2.x的时候,okHttpClint提供Cancel(tag)的方法来批量取消请求    //不过在3.x上批量请求的api被删除了,要取消请求只能在Callback中调用 call.cancel()    //因此这个tags参数只能由开发者自己编写函数来实现批量取消请求的操作    final Map
, Object> tags; }复制代码

请求响应的包装接口Call

public interface Call extends Cloneable {	Request request();	Response execute() throws IOException;	void enqueue(Callback responseCallback);	void cancel();}复制代码

请求事件队列,线程池分发

Call的实现类RealCallAsyncCall

@Override   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));  }//其中AsyncCall是RealCall的一个内部类,继承自Runnable,这样就能通过线程池来回调AsyncCall的execute函数final class AsyncCall extends NamedRunnable {    @Override     protected void execute() {        boolean signalledCallback = false;        try {            //getResponseWithInterceptorChain 拦截链的逻辑,也是发起请求的真正入口            Response response = getResponseWithInterceptorChain();            if (retryAndFollowUpInterceptor.isCanceled()) {                signalledCallback = true;                responseCallback.onFailure(RealCall.this, new IOException("Canceled"));            } else {                signalledCallback = true;                responseCallback.onResponse(RealCall.this, response);            }        } catch (IOException e) {            ...        }         ...    }}复制代码

递归Interceptor拦截器,发送请求

Response getResponseWithInterceptorChain() throws IOException {    // Build a full stack of interceptors.    List
interceptors = new ArrayList<>(); //用户自定义的拦截器(注意addAll 所以可以添加多个自定义的拦截器) 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); }复制代码

okHttp的核心部分就是这个Interceptor拦截链,每个Interceptor各自负责一部分功能,内部通过递归的方式遍历每一个Interceptor拦截器。递归逻辑在RealInterceptorChain类下

public final class RealInterceptorChain implements Interceptor.Chain {        //拦截器递归的入口    public Response proceed(Request request, StreamAllocation streamAllocation,               HttpCodec httpCodec, RealConnection connection) throws IOException {     ...    //拦截器递归的核心代码,根据interceptors列表执行每一个拦截器的intercept函数    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);	    ....    return response;  }}复制代码

递归结束后会获得请求响应,那么说明我们的request行为就在这个拦截链中,接下来我们先看看负责网络请求的那部分拦截器,从类名上就能比较容易的看出 ConnectInterceptorCallServerInterceptor这两个拦截器的主要工作。

网络连接拦截器ConnectInterceptor
public final class ConnectInterceptor implements Interceptor {      @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");        HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);        RealConnection connection = streamAllocation.connection();        return realChain.proceed(request, streamAllocation, httpCodec, connection);    }}复制代码

其中有几个对象说明一下

  • **StreamAllocation:**内存流的存储空间,这个对象可以直接从realChain中直接获取,说明在之前的拦截链中就已经赋值过

  • HttpCodec(Encodes HTTP requests and decodes HTTP responses): 对请求的编码以及对响应数据的解码

  • **realChain.proceed():**通知下一个拦截器执行

接下来看创建HttpCodec对象的newStream函数中做了些什么

//HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);public HttpCodec newStream(      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {   	    ...    try {      //findHealthyConnection内部通过一个死循环查找一个可用的连接,优先使用存在的可用连接,否则就通过	   //线程池来生成,其中多处使用 synchronized关键字,防止因为多并发导致问题      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);      synchronized (connectionPool) {        codec = resultCodec;        return resultCodec;      }    } catch (IOException e) {      throw new RouteException(e);    }  }复制代码

沿着代码往下走,你会发现实际上负责网络连接功能的类是一个叫RealConnection的类,该类中有一个connect的函数

RealConnection#connect

public void connect(int connectTimeout, int readTimeout, int writeTimeout,      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,      EventListener eventListener) {    ...            while (true) {      try {        if (route.requiresTunnel()) {          //这个函数最终还是会走到connectSocket()函数中          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);          if (rawSocket == null) {            // We were unable to connect the tunnel but properly closed down our resources.            break;          }        } else {          connectSocket(connectTimeout, readTimeout, call, eventListener);        }      }      ...  }    //最终调用的还是Socket对象来创建网络连接,包括connectTimeout,readTimeout等参数也是这个时候真正设置的。复制代码
网络请求拦截器 CallServerInterceptor

This is the last interceptor in the chain. It makes a network call to the server.

直接看CallServerInterceptor的intercept函数

@Overridepublic 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();        //发送请求头,也是网络请求的开始    httpCodec.writeRequestHeaders(request);        Response.Builder responseBuilder = null;    //请求不是get,并且有添加了请求体,写入请求体信息    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {      //如果请求头中有Expect:100-continue这么一个属性      //会先发送一个header部分给服务器,并询问服务器是否支持Expect:100-continue 这么一个扩展域      //okhttp3提供这么个判断是为了兼容http2的连接复用行为的      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {        //刷新缓存区,可以理解为向服务端写入数据        httpCodec.flushRequest();        realChain.eventListener().responseHeadersStart(realChain.call());        responseBuilder = httpCodec.readResponseHeaders(true);      }		      //写入请求body      if (responseBuilder == null) {        realChain.eventListener().requestBodyStart(realChain.call());        long contentLength = request.body().contentLength();        CountingSink requestBodyOut =            new CountingSink(httpCodec.createRequestBody(request, contentLength));        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);        request.body().writeTo(bufferedRequestBody);        bufferedRequestBody.close();        realChain.eventListener()            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);      }       ...    httpCodec.finishRequest();        //响应相关的代码    ...}复制代码

写入请求body的核心代码

//将请求体写入到BufferedSink中,而BufferedSink是另外一个类库Okio中的类CountingSink requestBodyOut =            new CountingSink(httpCodec.createRequestBody(request, contentLength));BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);request.body().writeTo(bufferedRequestBody);//httpCodec.finishRequest 最终会调用 sink.flush(),sink是BufferedSink的对象,BufferedSink在底层//会将其内的数据推给服务端,相当于是一个刷新缓冲区的功能httpCodec.finishRequest();复制代码

响应相关的代码

if (responseBuilder == null) {    realChain.eventListener().responseHeadersStart(realChain.call());  	//读取响应头,实际的返回流存放位置在okio库下的buffer对象中,读取过程中做了判断,只有当code==100时才会	 //有返回,不然抛出异常并拦截,所以下面这段代码肯定有响应头返回,不然直到超时也不会回调    responseBuilder = httpCodec.readResponseHeaders(false);}Response response = responseBuilder            .request(request)    .handshake(streamAllocation.connection().handshake())    .sentRequestAtMillis(sentRequestMillis)    .receivedResponseAtMillis(System.currentTimeMillis())    .build();int code = response.code();if (code == 100) {    //如果服务端响应码为100,需要我们再次请求,注意这里的100是响应码和之前的100不同    //之前的100是headerLine的标识码    responseBuilder = httpCodec.readResponseHeaders(false);    response = responseBuilder        .request(request)        .handshake(streamAllocation.connection().handshake())        .sentRequestAtMillis(sentRequestMillis)        .receivedResponseAtMillis(System.currentTimeMillis())        .build();    code = response.code();}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 {    //读取响应body    response = response.newBuilder()        .body(httpCodec.openResponseBody(response))        .build();}return response;复制代码

读取响应body HttpCodec#openResponseBody

public ResponseBody openResponseBody(Response response) throws IOException {    ...    Source source = newFixedLengthSource(contentLength);	return new RealResponseBody(contentType, contentLength, Okio.buffer(source));    ...}//openResponseBody将Socket的输入流InputStream对象交给OkIo的Source对象,然后封装成RealResponseBody(该类是ResponseBody的子类)作为Response的body.//具体读取是在RealResponseBody父类ResponseBody中,其中有个string()函数//响应主体存放在内存中,然后调用source.readString来读取服务器的数据。需要注意的是该方法最后调用closeQuietly来关闭了当前请求的InputStream输入流,所以string()方法只能调用一次,再次调用的话会报错public final String string() throws IOException {    BufferedSource source = source();    try {        Charset charset = Util.bomAwareCharset(source, charset());        return source.readString(charset);    } finally {        Util.closeQuietly(source);    }}复制代码

请求回调,数据解析

拿到请求回调的Response之后,再回到我们最开始调用的代码,

String url = "http://www.baidu.com";//'1. 生成OkHttpClient实例对象'OkHttpClient okHttpClient = new OkHttpClient();//'2. 生成Request对象'Request request = new Request.Builder()    .url(url)    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))    .build();Call call = okHttpClient.newCall(request);call.enqueue(new Callback() {    @Override    public void onFailure(@NonNull Call call, @NonNull IOException e) {    }    @Override    public void onResponse(@NonNull Call call, @NonNull Response response)  {        Headers responseHeaders = response.headers();        for (int i = 0, size = responseHeaders.size(); i < size; i++) {            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));        }        System.out.println(response.body().string());    }});复制代码

我们可以从Response对象中获取所有我们所需要的数据,包括header,body.至此,okHttp的网络请求的大致流程已经分析完成,至于还有部分没有讲到的拦截器就不再本文缀述了.有兴趣的可以看下文末的参考连接或者自行谷歌。

参考文章

扩展阅读

关于Http的请求头 Expect:100-Continue

Expect请求头部域,用于指出客户端要求的特殊服务器行为。若服务器不能理解或者满足Expect域中的任何期望值,则必须返回417(Expectation Failed)状态,或者如果请求有其他问题,返回4xx状态。Expect:100-Continue握手的目的,是为了允许客户端在发送请求内容之前,判断源服务器是否愿意接受请求(基于请求头部)。Expect:100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题。复制代码

http2比起http1.x的有点主要体现在以下几点

  • 新的数据格式, http基于文件协议解析,http2基于二进制协议解析,
  • 连接共享,多路复用(MultiPlexing)
  • header压缩,减小header的体积,使得请求更快
  • 压缩算法从gzip改成HPACK的算法,防破解
  • 重置连接表现更好,http1.x取消请求的是直接断开连接,http2则是断开某个连接的stream流
  • 更安全的SSL

参考资料

转载地址:http://xitpo.baihongyu.com/

你可能感兴趣的文章
VS Code非英语版本连接TFS错误解决方案
查看>>
angular5中使用jsonp请求页面
查看>>
sql in not in 案例用 exists not exists 代替
查看>>
使用newtonjson解决Json日期格式问题
查看>>
WEB前端资源代码:学习篇
查看>>
Nginx安装及配置详解【转】
查看>>
vue2.0 :style :class样式设置
查看>>
测不准原理主要指向微观
查看>>
排序算法java版,速度排行:冒泡排序、简单选择排序、直接插入排序、折半插入排序、希尔排序、堆排序、归并排序、快速排序...
查看>>
Android之ExpandableList扩展用法(基于BaseExpandableListAdapter)
查看>>
解决注册表映像劫持
查看>>
基于Redis架构的短信平台系统
查看>>
Java Daemon Control
查看>>
The Quick Guide to Python Eggs
查看>>
3D资源的后台加载
查看>>
怎样获取Web应用程序的路径
查看>>
xcode crash 查找 EXC_BAD_ACCESS 问题根源的方法
查看>>
PlotLegends 应用
查看>>
error: variable '__this_module' has initializer but incomplete type错误解决
查看>>
linux下为php添加mongodb扩展
查看>>