OKHttp源码解析(一)--初阶

作者: 隔壁老李头 | 来源:发表于2017-06-04 12:44 被阅读6958次

    这段时间老李的新公司要更换网络层,知道现在主流网络层的模式是RxJava+Retrofit+OKHttp,所以老李开始研究这三个项目的源代码,在更换网络层后,开始分享这个三个项目源码的分析。*
    本篇文章 主要讲解OKHttp源码解析(3.7.0)
    OKHttp官网
    github地址
    本文大体上分为11个部分

    本篇文章的主要内容如下:

      1. OkHttp介绍
      1. OkHttp使用
      1. OkHttp流程源码跟踪

    一、OKHTTP简介

    • 1.支持HTTP2/SPDY
    • 2.socket自动选择最好路线,并支持自动重连
    • 3.拥有自动维护的socket连接池,减少握手次数
    • 4.拥有队列线程池,轻松写并发
    • 5.拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩)基于Headers的缓存策略

    二、OKHTTP使用:

    1、GET请求

      OkHttpClient client = new OkHttpClient();
    
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      Response response = client.newCall(request).execute();
      return response.body().string();
    }
    

    2、POST请求

       public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
    
      OkHttpClient client = new OkHttpClient();
    
      RequestBody body = RequestBody.create(JSON, json);
    
      Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
    
      Response response = client.newCall(request).execute();
    
      return response.body().string();
    }
    

    三、OKHTTP源码流程分析

    (一)、OKHTTP 同步请求debug代码跟踪:

      OkHttpClient client = new OkHttpClient();
      Request request = new Request.Builder()
          .url(url)
          .build();
      Response response = client.newCall(request).execute();
    

    从上面代码所示,先是new了一个OKHttpClient对象。

    1、OKHttpClient类详解

    OKHttpClient类就比较简单了:

    • 1、里面包含了很多对象,其实OKhttp的很多功能模块都包装进这个类,让这个类单独提供对外的API,这种外观模式的设计十分的优雅。外观模式
    • 2、而内部模块比较多,就使用了Builder模式(建造器模式)。Builder模式(建造器模式)
    • 3、它的方法只有一个:newCall.返回一个Call对象(一个准备好了的可以执行和取消的请求)。

    而大家仔细读源码又会发现构造了OKHttpClient后又new了一个Rquest对象。那么咱们就来看下Request,说道Request又不得不提Response。所以咱们一起讲了

    2、Request、Response类详解

    Request Response

    • 1、Request、Response分别抽象成请求和相应
    • 2、其中Request包括Headers和RequestBody,而RequestBody是abstract的,他的子类是有FormBody (表单提交的)和 MultipartBody(文件上传),分别对应了两种不同的MIME类型
      FormBody :"application/x-www-form-urlencoded"
      MultipartBody:"multipart/"+xxx.
    • 3、其中Response包括Headers和RequestBody,而ResponseBody是abstract的,所以他的子类也是有两个:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。
    • 4、由于RFC协议规定,所以所有的头部信息不是随便写的,request的header与response的header的标准都不同。具体的见 List of HTTP header fields。OKHttp的封装类Request和Response为了应用程序编程方便,会把一些常用的Header信息专门提取出来,作为局部变量。比如contentType,contentLength,code,message,cacheControl,tag...它们其实都是以name-value对的形势,存储在网络请求的头部信息中。

    根据从上面的GET请求,显示用builder构建了Request对象,然后执行了OKHttpClient.java的newCall方法,那么咱们就看看这个newCall里面都做什么操作?

      /**
       * Prepares the {@code request} to be executed at some point in the future.
       */
      @Override public Call newCall(Request request) {
        return new RealCall(this, request, false /* for web socket */);
      }
    

    Call是个什么东西,那咱们看下Call这个类

    3、Call类详解

    Call: HTTP请求任务封装
    可以说我们能用到的操纵基本上都定义在这个接口里面了,所以也可以说这个类是OKHttp类的核心类了。我们可以通过Call对象来操作请求了。而Call接口内部提供了Factory工厂方法模式(将对象的创建延迟到工厂类的子类去进行,从而实现动态配置)
    Call接口提供了内部接口Factory(用于将对象的创建延迟到该工厂类的子类中进行,从而实现动态的配置).

    /**
     * A call is a request that has been prepared for execution. A call can be canceled. As this object
     * represents a single request/response pair (stream), it cannot be executed twice.
     */
    public interface Call extends Cloneable {
      /** Returns the original request that initiated this call. */
      Request request();
    
      /**
       * Invokes the request immediately, and blocks until the response can be processed or is in
       * error.
       *
       * <p>To avoid leaking resources callers should close the {@link Response} which in turn will
       * close the underlying {@link ResponseBody}.
       *
       * <pre>@{code
       *
       *   // ensure the response (and underlying response body) is closed
       *   try (Response response = client.newCall(request).execute()) {
       *     ...
       *   }
       *
       * }</pre>
       *
       * <p>The caller may read the response body with the response's {@link Response#body} method. To
       * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the
       * Response.
       *
       * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
       * not necessarily indicate application-layer success: {@code response} may still indicate an
       * unhappy HTTP response code like 404 or 500.
       *
       * @throws IOException if the request could not be executed due to cancellation, a connectivity
       * problem or timeout. Because networks can fail during an exchange, it is possible that the
       * remote server accepted the request before the failure.
       * @throws IllegalStateException when the call has already been executed.
       */
      Response execute() throws IOException;
    
      /**
       * Schedules the request to be executed at some point in the future.
       *
       * <p>The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually
       * immediately unless there are several other requests currently being executed.
       *
       * <p>This client will later call back {@code responseCallback} with either an HTTP response or a
       * failure exception.
       *
       * @throws IllegalStateException when the call has already been executed.
       */
      void enqueue(Callback responseCallback);
    
      /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */
      void cancel();
    
      /**
       * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
       * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
       */
      boolean isExecuted();
    
      boolean isCanceled();
    
      /**
       * Create a new, identical call to this one which can be enqueued or executed even if this call
       * has already been.
       */
      Call clone();
    
      interface Factory {
        Call newCall(Request request);
      }
    }
    

    在源码中,OKHttpClient实现了Call.Factory接口,返回了一个RealCall对象。那我们就来看下RealCall这个类

    4、RealCall类详解

    RealCall

    • 1、OkHttpClient的newCall方法里面new了RealCall的对象,但是RealCall的构造函数需要传入一个OKHttpClient对象和Request对象(PS:第三个参数false表示不是webSokcet).因此RealCall包装了Request对象。所以RealCall可以很方便地使用这两个对象。
    • 2、RealCall里面的两个关键方法是:execute 和 enqueue。分别用于同步和异步得执行网络请求。
    • 3、RealCall还有一个重要方法是:getResponseWithInterceptorChain,添加拦截器,通过拦截器可以将一个流式工作分解为可配置的分段流程,既增加了灵活性也实现了解耦,关键还可以自有配置,非常完美。

    所以client.newCall(request).execute();实际上执行的是RealCall的execute方法,现在咱们再回来看下RealCall的execute的具体实现

        @Override
        public Response execute() throws IOException {
            synchronized (this) {
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
            captureCallStackTrace();
            try {
                client.dispatcher().executed(this);
                Response result = getResponseWithInterceptorChain();
                if (result == null) throw new IOException("Canceled");
                return result;
            } finally {
                client.dispatcher().finished(this);
            }
        }
    

    首先是

            synchronized (this) {
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
    

    判断call是否执行过,可以看出每个Call对象只能使用一次原则。然后调用了captureCallStackTrace()方法。
    RealCall.java

      private void captureCallStackTrace() {
        Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
        retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
      }
    

    RealCall的captureCallStackTrace() 又调用了Platform.get().getStackTraceForCloseable()

    public class Platform {
      public static Platform get() {
        return PLATFORM;
      }
      /**
       * Returns an object that holds a stack trace created at the moment this method is executed. This
       * should be used specifically for {@link java.io.Closeable} objects and in conjunction with
       * {@link #logCloseableLeak(String, Object)}.
       */
      public Object getStackTraceForCloseable(String closer) {
        if (logger.isLoggable(Level.FINE)) {
          return new Throwable(closer); // These are expensive to allocate.
        }
        return null;
      }
    }
    

    其实是调用AndroidPlatform. getStackTraceForCloseable(String closer)方法。这里就不详细说了,后面详细说。
    然后retryAndFollowUpInterceptor.setCallStackTrace(),在这个方法里面什么都没做就是set一个object进去

    public final class RetryAndFollowUpInterceptor implements Interceptor {
      public void setCallStackTrace(Object callStackTrace) {
        this.callStackTrace = callStackTrace;
      }
    }
    

    综上所示captureCallStackTrace()这个方法其实是捕获了这个请求的StackTrace。
    然后进入了第一个核心类---Dispatcher的的execute方法了,由于下面是进入了关键部分,所以重点讲解下,代码如何:

        try {
          client.dispatcher().executed(this);
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } finally {
          client.dispatcher().finished(this);
        }
    

    看下OKHttpClient的dispatcher()方法的具体内容如下图

     //OKHttpClient.java
      public Dispatcher dispatcher() {
        return dispatcher;
      }
    

    大家发现client.dispatcher()返回的是Dispatcher对象,那么这个Dispatcher对象是何时创建的那?在OkHttpClient.java里面Build类里面的构造函数里面,如下图

    //OkHttpClient.java
    public static final class Builder {
       //其它代码先忽略掉
        public Builder() {
          dispatcher = new Dispatcher();
          //其它代码先忽略掉
        }
    }
    

    所以默认执行Builder()放到时候就创建了一个Dispatcher。那么咱们看下dispatcher里面的execute()是如何处理的

      /** Used by {@code Call#execute} to signal it is in-flight. */
      synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
      }
    

    里面发现是runningSyncCalls执行了add方法莫非runningSyncCalls是个list,咱们查看dispatcher里面怎么定义runningSyncCalls的。

      /** 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<>();
    

    原来runningSyncCalls是双向队列啊,突然发现Dispatcher里面定义了三个双向队列,看下注释,我们大概能明白readyAsyncCalls 是一个存放了等待执行任务Call的双向队列,runningAsyncCalls是一个存放异步请求任务Call的双向任务队列,runningSyncCalls是一个存放同步请求的双向队列。关于队列咱们在下篇文章里面详细介绍。

    执行完client.dispatcher().executed(this);要走到getResponseWithInterceptorChain();方法了里面了,看下这个方法是具体做什么的?

      Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        //添加开发者应用层自定义的Interceptor
        interceptors.addAll(client.interceptors());
        //这个Interceptor是处理请求失败的重试,重定向    
        interceptors.add(retryAndFollowUpInterceptor);
        //这个Interceptor工作是添加一些请求的头部或其他信息
        //并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        //这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
        interceptors.add(new CacheInterceptor(client.internalCache()));
        //这个Interceptor的职责是建立客户端和服务器的连接
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
          //添加开发者自定义的网络层拦截器
          interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
        //一个包裹这request的chain
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        //把chain传递到第一个Interceptor手中
        return chain.proceed(originalRequest);
      }
    

    发现 new了一个ArrayList,然后就是不断的add,后面 new了 RealInterceptorChain对象,最后调用了chain.proceed()方法。先看下RealInterceptorChain的构造函数。

     public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
          HttpCodec httpCodec, RealConnection connection, int index, Request request) {
        this.interceptors = interceptors;
        this.connection = connection;
        this.streamAllocation = streamAllocation;
        this.httpCodec = httpCodec;
        this.index = index;
        this.request = request;
      }
    

    发现什么都没做就是做了赋值操作,后面跟踪下chain.proceed()方法
    由于Interceptor是个接口,所以应该是具体实现类RealInterceptorChain的proceed实现

    public interface Interceptor {
      Response intercept(Chain chain) throws IOException;
    
      interface Chain {
        Request request();
    
        Response proceed(Request request) throws IOException;
    
        Connection connection();
      }
    }
    
    public final class RealInterceptorChain implements Interceptor.Chain{
      Response intercept(Chain chain) throws IOException;
    
          @Override 
          public Response proceed(Request request) throws IOException {
            return proceed(request, streamAllocation, httpCodec, connection);
          }
    
        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.
            RealInterceptorChain next = new RealInterceptorChain(
                    interceptors, streamAllocation, httpCodec, connection, index + 1, request);
            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;
        }
    
    }
    

    由于在构造RealInterceptorChain对象时候httpCodec直接赋予了null,所以下面代码直接略过。

       // 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");
        }
    

    然后看到在proceed方面里面又new了一个RealInterceptorChain类的next对象,温馨提示下,里面的streamAllocation, httpCodec, connection都是null,所以这个next对象和chain最大的区别就是index属性值不同chain是0.而next是1,然后取interceptors下标为1的对象的interceptor。由从上文可知,如果没有开发者自定义的Interceptor时,首先调用的RetryAndFollowUpInterceptor,如果有开发者自己定义的interceptor则调用开发者interceptor。

    这里重点说一下,由于后面的interceptor比较多,且涉及的也是重要的部分,而咱们这里主要是讲流程,所以这里就不详细和大家说了,由后面再详细讲解,后面的流程是在每一个interceptor的intercept方法里面都会调用chain.proceed()从而调用下一个interceptorintercept(next)方法,这样就可以实现遍历getResponseWithInterceptorChain里面interceptors的item,实现遍历循环,缩减后的代码如下:

      //RetryAndFollowUpInterceptor.java
    public Response intercept(Chain chain) throws IOException {
     //忽略部分代码
     response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
     //忽略部分代码
    }
     
    
    //BridgeInterceptor.java
    public Response intercept(Chain chain) throws IOException {
      //忽略部分代码
      Response networkResponse = chain.proceed(requestBuilder.build());
      //忽略部分代码
    }
    
    //CacheInterceptor.java
    public Response intercept(Chain chain) throws IOException {
       //忽略部分代码
       networkResponse = chain.proceed(networkRequest);
       //忽略部分代码
    }
    
    //ConnectInterceptor.java
    public Response intercept(Chain chain) throws IOException {
         //忽略部分代码
         return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }
    

    读过源码我们知道getResponseWithInterceptorChain里面interceptors的最后一个item是CallServerInterceptor.java,最后一个Interceptor(即CallServerInterceptor)里面是直接返回了response 而不是进行继续递归,具体里面是通过OKio实现的,具体代码,等后面再详细说明,CallServerInterceptor返回response后返回给上一个interceptor,一般是开发者自己定义的networkInterceptor,然后开发者自己的networkInterceptor把他的response返回给前一个interceptor,依次以此类推返回给第一个interceptor,这时候又回到了realCall里面的execute()里面了,代码如下:

      @Override 
      public Response execute() throws IOException {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        try {
          client.dispatcher().executed(this);
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } finally {
          client.dispatcher().finished(this);
        }
      }
    最后把response返回给get请求的返回值。至此整体GET请求的大体流程就已经结束了。(PS:最后别忘记走client.dispatcher().finished(this))
    

    大体流程如下图:

    image.png

    (二)、OKHTTP 异步请求debug代码跟踪:

    OkHttpClient client = new OkHttpClient();
    
    String run(String url) throws IOException {
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      Response response = client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });
    

    前面和同步一样new了一个OKHttp和Request。这块和同步一样就不说了,那么说说和同步不一样的地方,后面异步进入enqueue()方法

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

    由于executed默认为false,所以先进行判断是否为true,为true则直接跑异常,没有则设置为true,可以看出executed这个是一个标志,标志这个请求是否已经正在请求中,合同步一样先调用了captureCallStackTrace();然后调用 client.dispatcher().enqueue(new AsyncCall(responseCallback));client.dispatcher()返回的是Dispatcher对象所以实际调用的是Dispatcher的enqueue(),那么咱们进入源码看下

      //Dispatcher.java
      private int maxRequests = 64;
      private int maxRequestsPerHost = 5;
    
      synchronized void enqueue(AsyncCall call) {
      //如果正在执行的请求小于设定值即64,并且请求同一个主机的request小于设定值即5
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          //添加到执行队列,开始执行请求
          runningAsyncCalls.add(call);
          //获得当前线程池,没有则创建一个
          executorService().execute(call);
        } else {
          //添加到等待队列中
          readyAsyncCalls.add(call);
        }
      }
    

    根据源码和注释大家可以看到如果正在执行的异步请求小于64,并且请求同一个主机小于5的时候就先往正在运行的队列里面添加这个call,然后用线程池去执行这个call,否则就把他放到等待队列里面。执行这个call的时候,自然会去走到这个call的run方法,那么咱们看下AsyncCall.java这个类,而AsyncCall.java又继承自NamedRunnable.java咱们就一起看下他们的源码

    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();
    }
    
    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
    
        AsyncCall(Callback responseCallback) {
          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 {
            client.dispatcher().finished(this);
          }
        }
      }
    

    上面看到NamedRunnable的构造方法设置了name在的run方法里面设定为当前线程的name,而NamedRunnable的run方法里面调用了它自己的抽象方法execute,由此可见NamedRunnable的作用就是设置了线程的name,然后回调子类的execute方法,那么我们来看下AsyncCall的execute方法。貌似好像又回到了之前同步的getResponseWithInterceptorChain()里面,根据返回的response来这只callback回调。所以我们得到了OKHTTP的大体流程,如下图:

    OKHTTP大体流程.png

    三、OKHTTP类详解

    大体核心类主要下图:


    核心类.png

    最后给大家看一下整体的流程图

    流程.png

    相关文章

      网友评论

      • 三台东:博主人帅,文章思路也清晰,向大神学习。
      • carlwu_186:good work
      • ae4f849e9c20:摸拜大神
      • 念_念_不_忘:博主您好,我有一个问题没理解,麻烦您看下,就是Interceptor interceptor = interceptors.get(index);这一块为什么不从0开始呢,而是直接从1开始,再上一个方法中interceptors.addAll(client.interceptors());添加用户自定义的interceptor,如果没有的话,那么interceptors下标为0的应该是retryAndFollowUpInterceptor,这里直接从1开始不是把这个跳过了吗?
        adde01:在RealCall这个类的getResponseWithInterceptorChain()方法中
        去初始化第一个RealInterceptorChain时,构造里面index的参数传的就是0
        然后在执行RealInterceptorChain的proceed()方法时,Interceptor interceptor = interceptors.get(index);这里的index就是0
        不知道你说的直接从1开始是怎么发生的
      • zyhzyhzyh:厉害了
      • wheat7:流程清晰,学习了,感谢分享
      • 红江:深度好文,感谢分享
      • 无敌小草wx:厉害 !思路很清晰:+1: :+1: :+1:
        隔壁老李头:@无敌小草wx 谢谢
      • 程序猿BOSS:RealInterceptorChain.class 里面proceed()方法,interceptor.intercept(next);获取response,不是 “后面的流程是在每一个interceptor的interceptor方法里面都会调用chain.proceed()从而调用下一个interceptor的interceptor(next)方法”这里的单词应该也写错了,不知道我说的对不对。如果错了望大神谅解!
        隔壁老李头:已改好
        隔壁老李头:哈哈 是的,写错了,是intercept方法
      • 卖男孩的小火柴_5679:666写的真的可以的 就是那个set线程的名字那块没看明白 大神能给我说说吗
        隔壁老李头: @怒放摇滚英雄 不好意思没明白你的意思
        隔壁老李头: @卖男孩的小火柴_5679 set方法就是设置线程名字,是否我理解有误
        怒放摇滚英雄:加一,这个异步的时候为什么AsynCall的创建只需要回调接口。
      • efc38beebf6b:写的超好
        隔壁老李头: @yang小过 谢谢
      • 泽毛:牛逼
        隔壁老李头: @泽毛 谢谢
      • walker113:根据最新的源码讲解,厉害,谢谢!!!
      • _孑孓_:这10多篇写了多久啊
        隔壁老李头: @SpritMoon 一个多月吧
        隔壁老李头: @SpritMoon 写了一个多月吧
      • MigrationUK:好文,好详细👍
        隔壁老李头: @MigrationUK 谢谢
      • 陆地蛟龙:好文。
        隔壁老李头: @丿灬序曲 谢谢
        丿灬序曲:好文,很厉害
        隔壁老李头: @小蛤蟆智 谢谢
      • 杨旭_:自己边看源码,结合你的,看了好多遍,终于理解的差不多了。
        隔壁老李头: @1抹固执de笑 😂😂,我会继续努力的
      • 2860698d2edb:牛逼的一塌糊涂,源码有吗
        隔壁老李头: @AlphabetZhang 谢谢,上面不是有链接
      • 虚通磨忍:大牛,我就服你:+1:
        隔壁老李头: @刚刚好1016 哈哈,谢谢,刚哥太客气了
      • 1b7b18ec600c:好清晰的源码解析,我服了,大神
        隔壁老李头:@梦想之子 谢谢
      • 我是一只小壳蟆:真是厉害👍
      • 贝壳里的屋子:非常清晰详细,支持
      • ab64fd4eaee3:厉害!大神!!
        隔壁老李头:@zhuozhi 谢谢

      本文标题:OKHttp源码解析(一)--初阶

      本文链接:https://www.haomeiwen.com/subject/vlzktxtx.html