okhttp中的线程池及源码分析
来源:互联网 发布:latex for mac下载 编辑:程序博客网 时间:2024/05/22 19:43
okhttp3使用很常见,通常我们使用的时候是这样的:
//创建okHttpClient对象OkHttpClient mOkHttpClient = new OkHttpClient();//创建一个Requestfinal 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对重定向情况的处理。
- okhttp中的线程池及源码分析
- Java线程池--原理及源码分析
- Okhttp源码分析(五)连接池
- OkHttp源码分析
- OkHttp源码分析
- OkHttp源码分析
- OKHttp源码分析
- OkHttp源码分析
- OKHttp源码分析
- okhttp源码分析
- OkHttp源码分析
- Android中的线程和线程池及其源码分析:
- 源码分析Android中的线程和线程池
- OkHttp中的线程安全问题
- Java并发编程:线程池创建及源码分析
- Java ThreadPoolExecutor线程池原理及源码分析
- Okhttp使用和源码分析三(OkHttp源码分析)
- OKHttp框架源码分析(一)
- 创建一个Node.js应用
- ZOJ 3958Cooking Competition
- phalcon框架中的聚合
- 剑指offer题63
- codevs 2833 奇怪的梦境 拓扑排序
- okhttp中的线程池及源码分析
- Spring Cloud微服务(6)之spring cloud config分布式配置中心
- Maven战笔记
- 七月英语总结——不离开便不会陌生
- 1165: 实数的小数部分(指针专题)
- NetBeans生成JAR
- 导出Excel工具类
- Java中Set转List 和 TreeMap中实现自定义类作为key值
- PopupWindows与外部输入框焦点问题