OkHttp之网络请求流程(一)

本篇文章主要介绍OkHttp从发起请求到接收到结果的整个执行流程. 由上篇的概述可知,OkHttp支持同步和异步的请求,这里我们以异步请求为例进行分析

一,异步OkHttp请求示例

private void testOkHttp() throws IOException {
        final OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder().url("https://www.google.com.hk").build();

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

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                    Log.i(TAG,response.toString());
                    Log.i(TAG,response.body().string());
            }
        });
    }

上面的代码就实现了一个网络请求. 创建一个OkHttpClient对象,他包含了分发器,连接池,拦截器等等内容. 接下来创建一个请求对象,通过OkHttpClient的newCall()方法对其包装,然后添加请求队列,然后回调结果

Tip
对于OkHttpClient封装一个单实例,让所有的Http请求都重用它, OkHttp将会有更好的性能.因为每一个OkHttpClient实例都持有自己的连接池,线程池等,重用连接池和线程池能够减少网络访问延迟以及节省内存,减少资源的浪费

下面我们就详细的分析一下OkHttp详细的网络访问流程

二,OkHttp详访流程

1,创建HttpClient对象.

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

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    ... ...
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }

由上面可知HttpClient创建的时候,初始化了连接池,线程池,默认拦截器等等,为接下来的网络请求铺垫

2,创建Request对象

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

通过Request中的Builder构建一个请求对象
这个Request对象我们可以理解成一个请求Bean,里面封装了请求的url, 请求方法(post get put delete等),请求头,请求体,以及tag(用于批量取消)

 public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

接下来继续对Request对象进行包装,使其能够执行execute,入队enqueue, 取消cancel, 判断状态 isCancel和isExecuted
上面的call指向的是RealCall对象
我们看下Call接口

public interface Call extends Cloneable {
  Request request();
  Response execute() throws IOException;
  void enqueue(Callback responseCallback);
  void cancel();
  boolean isExecuted();
  boolean isCanceled();
  Call clone();
  interface Factory {
    Call newCall(Request request);
  }
}

3,将请求入队

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

OkHttpClient的dispatcher()方法返回的是一个Dispatcher对象,每个Dispatcher使用线程池来运行上面的Call任务.

接下来我们看下Dispacher的入队操作

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

runningAsyncCalls存放所有正在执行的异步任务
readyAsyncCalls存放所有正在等待执行的异步任务
当正在执行的请求小于最大请求数64且每个主机的最大请求数不超过5 则直接加入到正在执行的异步队列中且执行.
否则加入到正在等待执行的异步队列中等待执行.

**由上我们可知
请求相同主机的最大请求数目是5
所有请求最大数目是64
实际开发中,用户量一般的app服务器只有一个,那么一般最大的请求就是5
**
好了,一个请求封装准备了这么多, 顺利入队,下面我们看看他下面是怎么执行的

4,执行
RealCall封装了原始的Request,在RealCall中有一个非静态内部类AsyncCall,AsyncCall中的execute方法就是任务要执行的代码

final class AsyncCall extends NamedRunnable {
   ... ...
   ... ...
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

通过getResponseWithInterceptorChain方法 获取到Response对象,下面我们看下他的拦截链

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

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

RealInterceptorChain.java
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
   ... ...
    //取出下一个拦截器,递归调用
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
  ... ...
    return response;
  }

1)首先添加用户自定义的拦截器(如果有),接着依次添加重试拦截器,桥接拦截器,缓存拦截器,连接拦截器以及访问服务器的拦截器.
这些拦截器的添加顺序是固定的,不能改变
2)这些拦截器形成一个拦截链,每个拦截器专司其自己的职责, 将处理好的请求交给下一个拦截器,最后一个拦截器处理完毕后,再将其交给上一个拦截器,直到第一个, 最后将结果返回.这样一个责任链就形成了
3)每个拦截器都实现Interceptor接口

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}

4)对于拦截器之间的调用过程可能不太容易理解,下面针对2)给出一个示意图
Req: 请求Request缩写
Res: 响应Response缩写

基本流程图.png

5,结果回调
我们再将目光回到AsyncCall类


    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

当通过getResponseWithInterceptorChain()一系列的处理得到结果后.
1),会首先判断结果是否取消, 如果取消则回调传入的回调onFail中
否则,回调结果到onResponse中
2),接下来是扫尾操作
调用Dispatcher的finish方法

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

它主要做了两件事儿
A,将刚才已经完成的任务从runningAsyncCalls移除
B,如果runningAsyncCall小于5(每个主机的最大访问数量), 则从readyAsyncCalls移除添加到runningAsyncCalls中,并且执行

至此一个完整的OkHttp异步请求完毕
总的来说: 就是根据请求的url, 请求方法, 请求体构造一个Request的Bean, 然后将Request封装成一个RealCall,RealCall实现了Call接口,他有对执行逻辑的操作(取消,执行,判断执行的状态等), 接下来通过全局的OkHttpClient(拥有线程池,连接池,分发器等资源),利用分发器Dispatcher加入到执行队列或者等待队列中.
然后线程池执行RealCall的非静态内部类AsyncCall的execute方法, 然后通过getResponseWithInterceptorChain来进行一系列的拦截器操作处理,回调请求结果.
最后进行扫尾操作,移除正在执行队列的当前请求,将等待队列的请求添加到执行队列.

OkHttp的处理核心是在拦截器,每个拦截器都有自己的职责,下一篇文章详细分析各个拦截器具体都干了什么
OkHttp之拦截器(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 关于okhttp是一款优秀的网络请求框架,关于它的源码分析文章有很多,这里分享我在学习过程中读到的感觉比较好的文章...
    蕉下孤客阅读 3,588评论 2 38
  • 这篇文章主要讲 Android 网络请求时所使用到的各个请求库的关系,以及 OkHttp3 的介绍。(如理解有误,...
    小庄bb阅读 1,127评论 0 4
  • OkHttp解析系列 OkHttp解析(一)从用法看清原理OkHttp解析(二)网络连接OkHttp解析(三)关于...
    Hohohong阅读 20,949评论 4 58
  • OKHTTP异步和同步请求简单分析OKHTTP拦截器缓存策略CacheInterceptor的简单分析OKHTTP...
    未见哥哥阅读 35,276评论 6 27
  • 母亲总是很自然的将母亲节我们送给她的花,献在奶奶的照片前,然后撒上水。说起奶奶,有很多话想说,又不知从何说起...
    口口王木木阅读 361评论 0 0