3.OkHttp请求流程分析

Call和RealCall

经过上面的初始化之后 okhttpClient 调用public Call newCall(Request request) 方法去构建一个Call,

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

可以看到 真实的Call是RealCall ,get知识点 一般来说,一个组织或者个人的代码风格是差不多的 这里面Call的实现类是RealCall,其他的应该也是Real开头的。

这里我们发现他把OkHttpClient和Reques传了过去,他的构造方法是

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    //重试和跟进拦截器
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

    // TODO(jwilson): this is unsafe publication and not threadsafe.  
    // 这是不安全的发布,不是线程安全的。
    this.eventListener = eventListenerFactory.create(this);
  }

这里面比较面生的是的是RetryAndFollowUpInterceptor 按照字面意思重试和跟进拦截器 进去大概看一下

/**
 * This interceptor recovers from failures and follows redirects as necessary. It may throw an
 * {@link IOException} if the call was canceled.
 * 这个拦截器从故障中恢复,并根据需要遵循重定向。如果呼叫被取消,它可能会抛出IOException。
 */
public final class RetryAndFollowUpInterceptor implements Interceptor {
  /**
   * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
   * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
   * 我们应该尝试多少次重定向和认证挑战? Chrome遵循21次重定向; Firefox,curl和wget遵循20; Safari遵循16; HTTP / 1.0建议5。
   */
  private static final int MAX_FOLLOW_UPS = 20;

  private final OkHttpClient client;
  private final boolean forWebSocket;
  private StreamAllocation streamAllocation;
  private Object callStackTrace;
  private volatile boolean canceled;

  public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) {
    this.client = client;
    this.forWebSocket = forWebSocket;
  }
  
  (....此处省略以后的代码)

果然和字面意思一样,这面看参数的话最大支持20次的重定向。后面的暂时不需要看 等用到的时候再看也不迟,避免陷入之间树木不见森林的坑

后面又用工厂方法创建了一个EventListener 的类,看字面意思就是时间的监听类看里面的方法

Factory
fetchStart
dnsStart
dnsEnd
connectStart
secureConnectStart
secureConnectEnd
connectEnd
requestHeadersStart
requestHeadersEnd
requestBodyStart
requestBodyEnd
responseHeadersStart
responseHeadersEnd
responseBodyStart
responseBodyEnd
fetchEnd

通过这个事件我们大致能看出来OkHttp请求的流程,然后回到RealCall ,之后调用的是一个execute或者是enqueue 我们在Android项目里由于主线程是不允许有网络请求的,所以我们先来搞enqueue,话不多说,进去看

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //捕获呼叫堆栈跟踪
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

Already Executed 这个异常大家在平时用的时候应该偶尔会碰到,原因看到了吧。当你的这个请求应在运行的时候你在去调用的时候就异常了

然后第二个是捕获呼叫堆栈跟踪器,这个就忽略掉,看真正的重头戏

前方高能预警,提起精神看

Dispatcher

相关的更详细的分析在第五篇 OkHttp的请求调度分析

调用的OkHttpClient 的dispatcher的enqueue方法,dispatcher的初始化是在OkHttpClient的Builder里面 看上面的代码 是直接new 了一个 Dispatcher ,我们去dispatcher里看

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  /** Ready async calls in the order they'll be run. 按照他们将要运行的顺序进行准备就绪的异步调用 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. 运行异步调用包括尚未完成的取消请求*/
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet.  运行同步调用包括尚未完成的取消呼叫*/
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }
  
  (....此处省略N行代码)
  
   synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
}

我们看到他的构造函数就是一个空的 然后全局变量的话有个线程池,和三个双端队列 有可能有同学不知道Deque是什么,deque 即双端队列。是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。

我们看到enqueue里面就是操作运行异步调用包括尚未完成的取消请求的runningAsyncCalls,他的判断条件是:

1.当前队列里面的请求数量小于最大请求数也就是64

2.当前队列里面的链接的总host数量小于最大请求Host数

如果条件成立就添加到这个队列里面,否则的话就添加到readyAsyncCalls里,也就是按照他们将要运行的顺序进行准备就绪的异步调用的队列

加入到运行队列里后,执行executorService().execute(call);方法这个方法就是个new出了一个线程池,然后执行

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

可能关于线程池的一些东西大家不是特别清楚 这里稍微解释一下,首先是他的构造函数

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 
corePoolSize: 线程池维护线程的最少数量 
maximumPoolSize:线程池维护线程的最大数量 
keepAliveTime: 线程池维护线程所允许的空闲时间 
unit: 线程池维护线程所允许的空闲时间的单位 
workQueue: 线程池所使用的缓冲队列 
handler: 线程池对拒绝任务的处理策略

上面的SynchronousQueue可能一般同学看的不是特别熟悉这里解释一下:

SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除操作take,反过来也一样。

SynchronousQueue的一个使用场景的典型就是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。执行是调用execute方法。

峰回路转 ,回到ReallCall的enqueue里面

这里执行的正式AsyncCall,new AsyncCall(responseCallback) AsyncCalls是RealCall的一个内部类,继承NamedRunnable,NamedRunnable是一个实现了Runnable接口的抽象类,

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

这里他做了两件事

  1. 给当前线程设设置了个名字
  2. 新增了一个抽象方法execute

把握住这两个 再来看AsyncCall

AsyncCall

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      //命名规则 OkHttp+协议名+域名
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @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 {
        //调用dispatcer的finshed方法,(重要,在下文的请求调度分析会将)
        client.dispatcher().finished(this);
      }
    }
  }

上面比较重要的是这一句

  Response response = getResponseWithInterceptorChain();

如果重试和跟进拦截器没有被取消的话,返回请求成功调用 responseCallback.onResponse,如果中间有什么异常的话调用responseCallback.onFailure(RealCall.this, e);

getResponseWithInterceptorChain() 这个方法非常重要,是整个OkHttp请求的核心,他是组装了一系列的拦截链,进行链式调用,最后返回组装的请求结果

 /*一共五个拦截器  包括
  *   RetryAndFollowUpInterceptor 重试和跟进拦截器
  *   BridgeInterceptor           桥拦截器
  *   CacheInterceptor            缓存拦截器
  *   ConnectInterceptor          链接拦截器
  *   CallServerInterceptor       呼叫服务拦截器
  *
  *   RealInterceptorChain        实际拦截链
  * */
  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);
  }

接下来 我们来走进这些请求链的世界,去分析整个OkHttp请求的具体

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

推荐阅读更多精彩内容