美文网首页
okhttp源码分析第一篇:执行流程

okhttp源码分析第一篇:执行流程

作者: zzc不是自助餐 | 来源:发表于2019-04-02 14:31 被阅读0次

    费话不多说,先上代码

    final OkHttpClient okHttpClient = new OkHttpClient();
    Request request = new Request.Builder()
                  .url("https://www.baidu.com")
                  .build();
    Response response = okHttpClient.newCall(request).execute();
    

    这是一个同步get请求的代码。代码包含了三个步骤:
    1、创建okhttpClient;
    2、创建所需的请求信息(request对象);
    3、通过okhttpClient来执行该请求。

    下面就对以上三个步骤的源码进行分析。

    创建okhttpClient

    OkHttpClient通过建造者模式来创建对象:使用静态内部类Builder对象的参数来为OkHttpClient对象自身的参数进行赋值。

    public OkHttpClient() {
        this(new Builder());
      }
    
    OkHttpClient(Builder builder) {
        //通过builder中参数为自身的参数赋值
        this.dispatcher = builder.dispatcher;
        this.proxy = builder.proxy;
        this.protocols = builder.protocols;
        this.connectionSpecs = builder.connectionSpecs;
        ...
      }
    

    OkHttpClient.Builder对象的构造器中会对参数赋初始值,源码如下:

    public Builder() {
          dispatcher = new Dispatcher();  //请求调度器,通过线程池来对异步请求进行管理
          protocols = DEFAULT_PROTOCOLS;//http协议,默认支持TTP2.X和HTTP1.X
          connectionSpecs = DEFAULT_CONNECTION_SPECS;//对socket连接进行配置的类,包含URL地址,TLS版本和一些加密信息
          eventListenerFactory = EventListener.factory(EventListener.NONE);//请求监听器
          proxySelector = ProxySelector.getDefault();
          ...
          connectionPool = new ConnectionPool();//默认创建一个连接池,多个OkHttpClient实例会导致创建多个线程池对象。
          dns = Dns.SYSTEM;
          followSslRedirects = true;
          followRedirects = true;//默认支持重定向
          retryOnConnectionFailure = true;//默认支持失败重连
          callTimeout = 0;
          connectTimeout = 10_000;//连接超时默认10秒
          readTimeout = 10_000;//读取超时
          writeTimeout = 10_000;//写超时
          pingInterval = 0;
        }
    

    有时OkHttp的默认配置不能满足我们的需求,例如我们需要将读取超时时间设为30秒并且禁止失败重连。那我们需要通过Builder的build()方法来创建OkHttpClient对象。

    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .readTimeout(10L, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(false)
                    .build();
    

    当我们对Buider对象配置完需要修改的参数之后,通过build()方法来创建OkHTTPClient对象。

    //OkHttpClient静态内部类
    public static final class Builder {
        ...
        public Builder readTimeout(long timeout, TimeUnit unit) {
            readTimeout = checkDuration("timeout", timeout, unit);
            return this;
        }
        ...
        public Builder retryOnConnectionFailure(boolean retryOnConnectionFailure) {
              this.retryOnConnectionFailure = retryOnConnectionFailure;
              return this;
        }
        ...
        //  配置完成之后通过此方法创建OkHttpClient对象
        public OkHttpClient build() {
              return new OkHttpClient(this);
        }
    }
    

    okhttp建议我们只创建一个OkHttpClient实例并在整个应用中使用它(这是因为多个实例会导致创建多个连接池对象、多个调度器以及多个缓存,浪费内存不说还有可能因为多个chache使用同一个缓存目录导致crash)。而我们的参数配置(如超时、失败重连、拦截器等)是在创建OKHttpClient对象的时候设置的。如果要为某个请求单独配置参数,则需要通过OkHttpClient的newBuilder()方法来创建一个与原始client实例共享连接池、调度器和配置的builder对象。

    //原始创建的okhttpClient 对象
    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .readTimeout(20L, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(false)
                    .build();
    
    //某个请求所需的okhttpClient对象
    OkHttpClient otherClient = okHttpClient.newBuilder()
                    .readTimeout(30L, TimeUnit.SECONDS)
                    .build();
    

    源码如下,当调用newBuilder()方法时,会将当前okhttpClient对象(也就是原始的实例对象)传递给Builder的构造器,那我们创建的builder对象则和原始实例对象共用相同的参数。

    public Builder newBuilder() {
        return new Builder(this); //将原始的client对象传递给构造器
     }
    Builder(OkHttpClient okHttpClient) {
          //新创建的builder对象和原始的client对象使用相同的参数,我们只需将我们所需要改变的参数设置后即可
          this.dispatcher = okHttpClient.dispatcher;
          this.proxy = okHttpClient.proxy;
          this.protocols = okHttpClient.protocols;
          this.connectionSpecs = okHttpClient.connectionSpecs;
          ...
    }
    
    

    以上就是创建OkHttpClient对象的源码分析,总结如下:

    • OkHttpClient对象的创建使用的是建造者设计模式
    • 通过new OkHttpClient()方式创建默认配置的对象
    • 通过new OkHttpClient.Builder()来修改配置然后通过build()方法创建对象
    • 通过OkHttpClient的newBuilder()方法来创建与原始实例对象共享连接池、调度器和配置的Builder对象,通过修改builder对象的部分参数来创建满足单个请求所需要的特殊OkHttpClient对象.

    创建请求信息对象(request对象)

    Request对象中包含一个URL地址、一个method(请求方法名称)、一个Headers(请求头对象)、一个RequestBody(请求体对象)。Request对象的创建同样是使用的建造者模式,对于请求对象来说url地址是必要的参数,所以request并没有提供默认构造器供我们调用。Request源码稍微简单点,源码如下:

    public static class Builder {
        public Builder() {
            this.method = "GET";
            this.headers = new Headers.Builder();
        }
        public Request build() {
            if (url == null) throw new IllegalStateException("url == null");
            return new Request(this);
        }
    }
    
      Request(Builder builder) {
          this.url = builder.url;//请求地址
          this.method = builder.method;//请求方法
          this.headers = builder.headers.build();//请求头
          this.body = builder.body;//请求体,
          this.tags = Util.immutableMap(builder.tags);
      }
    

    从上面代码可知,默认会指定请求方法为get方法。通过Builder的build方法创建了我们的request对象。

    Header内部以字符串数组的形式保存键值对数据。但操作类似于MultiMap。当我们添加请求头时,可以通过header()或者addHeader()方法来添加。从源码可以很直观的看出两者的区别。

    public final class Headers {
      private final String[] namesAndValues;//使用数组保存请求头信息
    }
    
    //header方法会保证键值对一一对应
    public Builder header(String name, String value) {
          headers.set(name, value);
          return this;
    }
    public Builder set(String name, String value) {
          checkName(name);
          checkValue(value, name);
          removeAll(name);//移除所有name对应的value值
          addLenient(name, value);//将新value值添加进去
          return this;
    }
    //遍历数组删除name,以及该name对应的值
    public Builder removeAll(String name) {
          for (int i = 0; i < namesAndValues.size(); i += 2) {
            if (name.equalsIgnoreCase(namesAndValues.get(i))) {
              namesAndValues.remove(i); // name
              namesAndValues.remove(i); // value
              i -= 2;
            }
          }
          return this;
    }
    //现将键name添加进去,然后将值value添加进去,保证了偶数位都为键,基数为都为值。
    Builder addLenient(String name, String value) {
          namesAndValues.add(name);
          namesAndValues.add(value.trim());
          return this;
    }
    
    //addHeader允许一个键对应多个值
    public Builder addHeader(String name, String value) {
          headers.add(name, value);
          return this;
    }
    public Builder add(String name, String value) {
          checkName(name);
          checkValue(value, name);
          return addLenient(name, value);//将值添加到键对应的集合中
    }
    

    当我们使用post方式提交数据的时候,数据都是保存在RequestBody(方法体)中的。RequestBody是一个抽象类,可以通过RequestBody 提供的静态方法create()创建。

    public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
          final int offset, final int byteCount) {
        if (content == null) throw new NullPointerException("content == null");
        Util.checkOffsetAndCount(content.length, offset, byteCount);
        //直接返回一个匿名内部类对象
        return new RequestBody() {
          @Override public @Nullable MediaType contentType() {
            return contentType;//数据类型 指定非空
          }
    
          @Override public long contentLength() {
            return byteCount;
          }
    
          @Override public void writeTo(BufferedSink sink) throws IOException {
            sink.write(content, offset, byteCount);//写入数据
          }
        };
      }
    

    从源码可知,创建一个RequestBody对象,需要指定他的数据类型(MediaType)。数据以字节数组(这样就可以传输文件、照片等数据)的形式通过BufferedSink写入。

    创建Request对象总结如下:

    • Request对象的创建使用的是建造者模式
    • Request类内部包含url地址、方法名称method、请求头信息headers以及请求体requestBody
    • 请求头信息headers的数据以数组形式存放,支持一对一(通过header()方法添加),一对多(通过addHeader()方法添加)的键值对。
    • 请求体是一个抽象类,可以通过RequestBody的静态方法create()来创建requestBody对象。

    执行请求

    okHttpClient.newCall(request).execute();
    

    首先通过OkHttpClient的newCall()方法创建一个call对象(call是一个接口,它的实现是RealCall)。

    @Override public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
     }
    
    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.transmitter = new Transmitter(client, call);
        return call;
      }
    

    RealCall类代码如下,realCall对象持有okhttpClient、request对象的引用。

    final class RealCall implements Call {
      final OkHttpClient client;
      private Transmitter transmitter;
      final Request originalRequest;
      final boolean forWebSocket;
      private boolean executed;//是否已经执行
    
      private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
      }
    }
    

    对于同步请求来说,通过realCall对象的excute()方法执行该请求。excute()方法中显示调用了dispatcher的excuted()方法,接着调动核心方法getResponseWithInterceptorChain遍历拦截器获取响应,最后调用dispatcher的finished方法结束请求。

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

    okhttpClient的dispatcher()方法直接返回一个Dispatcher(请求调度器对象)。Dispatcher内部维护着三个请求队列和一个线程池对象,源码如下。

    public final class Dispatcher {
      private int maxRequests = 64;//最大同时请求数
      private int maxRequestsPerHost = 5;//每个主机最大请求数
      private @Nullable Runnable idleCallback;
      private @Nullable ExecutorService executorService;//线程池对象
      private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//等待执行的异步请求队列
      private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在执行的异步请求队列
      private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//正在执行的同步请求队列
    }
    

    接下来看看Dispatcher的excute()方法,直接将同步请求的call对象添加到同步请求队列中。

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
      }
    

    下面看看核心方法getResponseWithInterceptorChain()的源码。

    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(new RetryAndFollowUpInterceptor(client));
        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, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        boolean calledNoMoreExchanges = false;
        try {
          Response response = chain.proceed(originalRequest);
          if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
          }
          return response;
        } catch (IOException e) {
          calledNoMoreExchanges = true;
          throw transmitter.noMoreExchanges(e);
        } finally {
          if (!calledNoMoreExchanges) {
            transmitter.noMoreExchanges(null);
          }
        }
    }
    

    方法内部拆分如下:

    • 第一步,将一系列拦截器添加到拦截器集合当中。添加顺序为:自定义的应用拦截器,okhttp定义的默认拦截器,自定义的网络拦截器,CallServerInterceptor(网络请求拦截器)
    • 第二步,通过拦截器集合构造一个拦截器链对象RealInterceptorChain
    • 第三步,通过拦截器器链RealInterceptorChain的proceed()方法递归调用拦截器获得相应response

    对于第一步骤,okhttp添加的默认拦截器如下

    //用于失败重连,请求跟踪
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    
    //用于添加一些默认请求头信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    
    //用于配置缓存信息
    interceptors.add(new CacheInterceptor(client.internalCache()));
    
    //用于获取网络连接
    interceptors.add(new ConnectInterceptor(client));
    
    //真正的执行网络请求获取响应response
    interceptors.add(new CallServerInterceptor(forWebSocket));
    

    对于第二步骤,创建拦截器连对象代码如下,这里注意创建时index传的是0

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
          @Nullable Exchange exchange, int index, Request request, Call call,
          int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;
        this.transmitter = transmitter;
        this.exchange = exchange;
        this.index = index;
        this.request = request;
        this.call = call;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.writeTimeout = writeTimeout;
      }
    

    对于第三步骤,看看proceed()的源码

    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
          throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
        calls++;
        ...
        //构造新的拦截器链对象
        RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
            index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    
        //获得拦截器集合中的第index位置的对象
        Interceptor interceptor = interceptors.get(index);
    
        //调用interceptor的intercept方法
        Response response = interceptor.intercept(next);
    
        ...
    
        // Confirm that the intercepted response isn't null.
        if (response == null) {
          throw new NullPointerException("interceptor " + interceptor + " returned null");
        }
    
        if (response.body() == null) {
          throw new IllegalStateException(
              "interceptor " + interceptor + " returned a response with no body");
        }
    
        return response;
      }
    

    从上述代码可知会首先将interceptor集合中的第一个拦截器取出来并调用他的intercept()方法。我们以RetryAndFollowUpInterceptor为例来看看他的intercept()方法。

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Transmitter transmitter = realChain.transmitter();
    
        ...
          try {
            response = realChain.proceed(request, transmitter, null);
            success = true;
          } catch (RouteException e) {
            // The attempt to connect via a route failed. The request will not have been sent.
            if (!recover(e.getLastConnectException(), transmitter, false, request)) {
              throw e.getFirstConnectException();
            }
            continue;
          } catch (IOException e) {
            // An attempt to communicate with a server failed. The request may have been sent.
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            if (!recover(e, transmitter, requestSendStarted, request)) throw e;
            continue;
          } finally {
            // The network call threw an exception. Release any resources.
            if (!success) {
              transmitter.exchangeDoneDueToException();
            }
          }
      }
    

    在intercept()方法内部又会去调用realChain.proceed()方法,通过index+1,获取到intercept集合中的下一个拦截器对象。继而递归调用所有拦截器的intercept()方法,最后调用到CallServerInterceptor的intercept方法,在CallServerInterceptor中会真正发起网络请求获取响应返回。

    到这里整个getResponseWithInterceptorChain()方法执行完毕并返回响应response信息。接着回到realCall的excute()方法,在finally语句快中会调用dispatcher的finish方法。

    //同步请求finish()方法,将同步请求队列和请求call交给私有方法finished()处理
    void finished(RealCall call) {
        finished(runningSyncCalls, call);//调用内部私有方法finished()
      }
    
    private <T> void finished(Deque<T> calls, T call) {
        Runnable idleCallback;
        synchronized (this) {
          //移除已完成的call对象
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          idleCallback = this.idleCallback;
        }
    
        //从异步等待队列中取出call放入异步执行队列中去执行(针对于异步请求)
        boolean isRunning = promoteAndExecute();
    
        if (!isRunning && idleCallback != null) {
          idleCallback.run();
        }
      }
    

    以上分析了okhttp同步请求excute()方法的源码,接下来看看异步请求enqueue()方法。异步请求会调用realCall的enqueue()方法。

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

    内部调用了dispatcher的enqueue方法,传递对象为AsyncCall,AsyncCall是一个Runnable对象。

    void enqueue(AsyncCall call) {
        synchronized (this) {
          readyAsyncCalls.add(call);
    
          if (!call.get().forWebSocket) {
            AsyncCall existingCall = findExistingCallWithHost(call.host());
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
          }
        }
        promoteAndExecute();
      }
    

    dispatcher的enqueue方法中,直接将请求对象AsyncCall添加到待执行的队列中,然后调用promoteAndExecute()方法。

    private boolean promoteAndExecute() {
        assert (!Thread.holdsLock(this));
    
        List<AsyncCall> executableCalls = new ArrayList<>();
        boolean isRunning;
        synchronized (this) {
          for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
    
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
    
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
    
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          asyncCall.executeOn(executorService());
        }
    
        return isRunning;
      }
    

    promoteAndExecute()方法逻辑是这样的:首先遍历待执行的异步请求队列readyAsyncCalls,如果当前正在执行的异步请求数量(runningAsyncCalls.size())没有达到最大值,并且每个主机的请求最大值也没打到,则将该异步请求添加到请求集合executableCalls 中。接着遍历executableCalls中的AsyncCall 对象调用它的executeOn()方法执行网络请求。

    void executeOn(ExecutorService executorService) {
          assert (!Thread.holdsLock(client.dispatcher()));
          boolean success = false;
          try {
            executorService.execute(this);
            success = true;
          } catch (RejectedExecutionException e) {
            InterruptedIOException ioException = new InterruptedIOException("executor rejected");
            ioException.initCause(e);
            transmitter.noMoreExchanges(ioException);
            responseCallback.onFailure(RealCall.this, ioException);
          } finally {
            if (!success) {
              client.dispatcher().finished(this); // This call is no longer running!
            }
          }
        }
    

    这里要说明一下AsyncCall是一个Runnable对象,他继承自NamedRunnable,NamedRunnable实现了Runnable接口。线程池执行Runnable对象时会去调用它的run()方法,而在NamedRunnable的run方法中调用了excute()方法,所以最终executorService.execute(this);会触发AsyncCall对象的excute()方法被调用。

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

    最后看看AsyncCall的excute()方法

    @Override protected void execute() {
          boolean signalledCallback = false;
          transmitter.timeoutEnter();
          try {
            Response response = getResponseWithInterceptorChain();
            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对象,最后调用callback的onresponse方法回调结束本次异步请求。

    相关文章

      网友评论

          本文标题:okhttp源码分析第一篇:执行流程

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