OkHttp阅读笔记(一)

因为日常使用原因,目前只关心Http1.1协议相关的代码。
第一篇只关心执行流程,后续会仔细分析一些细节。
从一个简单的例子进入

 okHttpClient.newCall(new Request.Builder().build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}
            @Override
            public void onResponse(Call call, Response response) throws IOException {}
        });

从代码的角度上面来看,意思就是创建一个新的请求,然后将请求放入队列当中。
接着看实际的请求

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

通过装饰模式,Request最终都会封装成RealCall,接着看一下enqueue操作

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;//标记当前请求开始执行
    }
    captureCallStackTrace();
    // 这里将回调封装到AsyncCall中
    // 然后将当前请求送入OkHttpClient中的Dispatcher中
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

工作最后到了Dispatcher中,OkHttpClient创建的时候会创建一个Dispatcher

 private int maxRequests = 64;//calls的最大并发量
 private int maxRequestsPerHost = 5;//同一个请求host的最大允许并发请求
 //这两个参数共同决定了一个call是否可以进入执行状态

 /** Ready async calls in the order they'll be run.
  * 双向队列,用于放置那些准备开始异步请求的calls
  * */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet.
   * 双向队列,用于放置那些在运行中的异步calls,也包括那些已经执行然后被要求取消的calls(因为它们还没有完成)
   * */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

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

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //如果当前执行中的异步请求数目和已经执行的同一host数目没有超过最大值
      //添加到执行中队列并开始异步执行请求
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //否则进入准备执行队列当中
      readyAsyncCalls.add(call);
    }
  }

因为OkHttp不是单纯的基于Android平台实现,更应该看做java实现,所以说会看到最大并发量可以达到64之大,所以说平常做Android开发的时候还是要根据情况进行管理。
执行线程池为缓存线程池,用于更快的响应新的任务,尝试重用线程或者直接新起线程执行任务,这个对于快速的任务来说是一个不错的选择,不过对于Android平台来说,可能还是使用有所限制比较好,比方说使用固定核心线程数的线程池。
接下来任务开始执行,说明AsyncCall本身就是一个Runnable,接着看run()方法

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

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

逻辑比较清晰,实际上就是在子线程中进行请求操作,并且对响应进行封装。
然后对结果进行callback回调,最后进行以下Dispatcher中的数据处理即可。
请求中出现任何异常或者被取消都会回调失败,这点稍微需要注意。
回调默认在子线程中,Android在使用的时候稍微注意线程的切换问题。
接下来看一下getResponseWithInterceptorChain()操作

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()));
    //处理http协议的缓存逻辑
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //处理socket建立连接或者复用连接过程,总之这里会建立连接
    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中

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
                          RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    // 为了获取响应结果,每一个拦截器最终都会通过RealChain.proceed来获得结果
    // 那么在这里会调用下一个拦截器
    // index会在调用的之前的拦截器的基础上+1
    RealInterceptorChain next = new RealInterceptorChain(
            interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //index默认为0
    Interceptor interceptor = interceptors.get(index);
    //也就是说拦截器的执行顺序满足添加顺序,然后内部会进行循环
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

简要描述一下这个方法的意义,首先这里会获得RealCall中设置的拦截器列表,然后拿出当前标记的拦截器(从0开始),并且将下一个拦截器交给当前拦截器。
每一个拦截器的最终执行结果都是Response,这意味这如果拦截器自身产生Response返回,则调用链开始往回走。
如果通过之前传入的下一个拦截器执行,这样会导致调用链会继续向下执行,最终再向上执行。

总结

这一篇只是描述了执行的一个流程,从下一篇开始会描述每一个拦截器的意义。

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

推荐阅读更多精彩内容