okhttp中的线程池及源码分析

本文详细分析了OkHttp3中的线程池机制,指出线程池仅在enqueue方法时启用,能自动调整线程数量。通过源码跟踪,解释了Request的构建过程、Call的异步与同步执行方式,特别是enqueue方法触发Dispatcher的enqueue操作,以及在AsyncCall中如何执行任务。文中还揭示了RealCall的getResponseWithInterceptorChain()方法中通过一系列拦截器完成HTTP请求的过程,最后在CallServerInterceptor中执行实际的HTTP请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

okhttp3使用很常见,通常我们使用的时候是这样的:

//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建一个Request
final Request request = new Request.Builder()
                        .url(url)
                        .build();
        Call call = mOkHttpClient.newCall(request);
        //请求加入调度
        call.enqueue(new Callback() {
            @Override
            public void onResponse(final Response response) throws IOException {
            }

            @Override
            public void onFailure(Request arg0, IOException arg1) {
                // TODO Auto-generated method stub
            }
        });

看下okhttpClient的构造函数,

public OkHttpClient() {
        this.dispatcher = new Dispatcher();
        ***
    }

这里有个dispatcher, 分析下Dispatcher的源码:

public Dispatcher() {
  }

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

看到了没,这里有两个构造函数,有一个创建线程池的函数,但并不会在开始调用,可以告诉大家,这里只有当你使用enqueue方法的时候,才会用到这个线程池,okhttp所用的线程池有点类似于newCacheExecutorService,好处是会根据程序的运行情况自动来调整线程池中的线程数量,我这里对这个线程池写过demo,假如你一次有30个请求,它会同步的一次将请求放到非核心线程中,因为非核心线程的数量为系统最大值,所以只要系统有空闲,就会创建线程并顺序处理。
接下来看下Request的build函数,这里用了建造者模式来存放Request的请求参数。
接下来看下Call call = mOkHttpClient.newCall(request);这句话,可以看到真正的调用是RealCall这个类,而这里和okhttp3和okhttp2的区别在于Call这个类,okhttp3中是个接口,而okhttp2中是个对象。

最后是RealCall的enqueue方法,这个方法是重点,我们进去分析下:

client.dispatcher().enqueue(new AsyncCall(responseCallback));

可以看到调用的是Dispatcher的enqueue方法,进去看看,

synchronized void enqueue(AsyncCall call) {
        if(this.runningCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
            this.runningCalls.add(call);
            this.getExecutorService().execute(call);
        } else {
            this.readyCalls.add(call);
        }

    }

这里判断同时加入的任务有没有超过一些限制,然后执行线程池中的任务,进到Call.AsyncCall方法中:

public abstract class NamedRunnable implements Runnable {
     public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(this.name);

        try {
            execute();
        } finally {
            Thread.currentThread().setName(oldName);
        }

    }
}

先看下NamedRunnable 类,可以看到执行了execute方法:

 final class AsyncCall extends NamedRunnable {
    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的onResponse方法,注意,因为线程池开启的任务是异步任务,而这里的回调都是在子线程中执行的,因此不能直接去更新UI,这也是单独用okhttp不是很方便的原因。

我们再看RealCall中的方法,有个excute方法,这个方法和enqueue方法都是执行任务,而excute执行的是同步任务,如果你像上面一样写下如下代码:

Call call = mOkHttpClient.newCall(request);
call.execute();

肯定会报错误的,因为这时执行的时候会认为在主线程执行的http请求,而http请求是必须要在子线程执行的。
我们再看下Response response = getResponseWithInterceptorChain();这个函数,大家应该都很好奇okhttp看到这里,好像也没什么特别的,封装了两种方式,异步和同步的请求,开了个线程池,那么真正的请求在哪里做的呢?答案就在这个方法里面,我们进去看下:

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

这里给interceptors添加了6个拦截器,然后调用RealInterceptorChain的proceed方法,再进去看下:

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection 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;
  }
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;
  }

代码很长,核心内容在这里,仔细分析一下,从interceptors里面根据index索引取出拦截器,然后调用intercept方法,并传入RealInterceptorChain 对象,而在intercept方法中都有调用RealInterceptorChain 的proceed方法,这说明什么?他们在循环调用啊!!!我勒个去。。。。根据index的不同,intercept方法中传入的是下一个拦截器对象。那么为什么要这样呢?拦截器里面做了什么,看来我么要去分析下拦截器里面的intercept方法了,先看第一个retryAndFollowUpInterceptor:

response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);

这里是循环调用的体现,流程是先去处理其他5个拦截器,得到response之后再来这边处理,那么接下来看第二个拦截器BridgeInterceptor,同样在intercept方法里面调用了如下函数:

 Response networkResponse = chain.proceed(requestBuilder.build());

意味着再去处理剩余的4个处理器,等待response进行处理,如此类推,最后处理的拦截器就是CallServerInterceptor,我们看下intercept方法:

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);//写入请求头

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {//判断是否有body数据
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
        // being reused. Otherwise we're still obligated to transmit the request body to leave the
        // connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
}

看到了,这里就是真正的http请求,但我们发现这里的http请求不是用HttpUrlConnection,那他是怎么做的呢?
先是封装了Request对象,然后写入到HttpCodec中,接着判断是否带body数据,如果携带了,写入到request.body()中,然后刷新httpCodec,httpCodec中的source和sink,对应着socket请求中的输入和输出,从HttpCodec中读取响应的头信息,返回Response.Builder。

httpCodec.finishRequest();//这里是真正的执行请求

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

最后拿到Response数据:

Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

后面是拿到的response数据进行处理。
前面说了,其他的拦截器都是对这里的Response进行结果的处理。CacheInterceptor主要是对结果进行缓存的控制,retryAndFollowUpInterceptor对重定向情况的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值