前言
对OKhttp的源码看了有一段时间了,感觉有点体会,准备写点东西,深刻深刻,不然过段时间就忘了O(∩_∩)O哈哈~
网上已经有了很多对OKhttp3的源码分析文章,说的也都比较好,但是觉得还是看别人写的再好终究是别人的东西,自己写的过程中会对OKhttp的理解更加深刻,而且1000个人的眼里有一千个哈姆雷特,每个人对OKhttp的理解,认识,还是会有不同的。
对OKhttp的源码分析,一篇文章也是说不完的,能说完的话,那也太省略,长篇大论的文章,也很难让人看下去,所以,准备写一个关于OKhttp源码的分析的系列文章,本篇主要写的是OKhttp的工作流程,并对流程中的要点进行简要概述,后续的文章中在精要分析。
概述
官网OverView
看官方的最准确。
基本用法
先看基本用法,通过他的用法,来分析他的原理。
OKhttp请求分为同步请求和异步请求,代码如下:
同步请求:
OkHttpClient client = new OkHttpClient();String run(String url) throws IOException {Request request = new Request.Builder().url(url).build();Response response = client.newCall(request).execute();return response.body().string();
}
异步请求:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {}});
同步请求比较简单,所以我们来分析,异步请求,异步请求的原理明白了,同步的自然而然就知道了。
流程源码剖析
从代码中我们不难看出来,OKhttpClient,Request,Response这些都是比较重要的类。我们看OKhttpClient的构造方法 new OKhttpClient();
public OkHttpClient() {this(new Builder());}
他的无参构造方法中调用了有Builder参数的构造方法,自己new 了一个Builder对象穿了进去。我们接着看new Builder()方法。
public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;......connectTimeout = 10_000;readTimeout = 10_000;writeTimeout = 10_000;pingInterval = 0;}
发现该构造方法中初始化了一些对象,赋值了变量。
OkHttpClient(Builder builder) {this.dispatcher = builder.dispatcher;this.proxy = builder.proxy;this.protocols = builder.protocols;......this.connectTimeout = builder.connectTimeout;this.readTimeout = builder.readTimeout;this.writeTimeout = builder.writeTimeout;this.pingInterval = builder.pingInterval;if (interceptors.contains(null)) {throw new IllegalStateException("Null interceptor: " + interceptors);}if (networkInterceptors.contains(null)) {throw new IllegalStateException("Null network interceptor: " + networkInterceptors);}}
上面代码我们看到,把Builder的变量的值赋给了OKhttpClient,我省略的部分代码,不然代码量会比较多。
这种Builder建造者模式的设计在OKhttp项目中有很多,比如Request,Response,CacheControl等类,都采用了Builder模式来设计。比较灵活方便。
然后我们看Request.Builder.url(url)方法。
public Builder url(String url) {if (url == null) throw new NullPointerException("url == null");// Silently replace web socket URLs with HTTP URLs.if (url.regionMatches(true, 0, "ws:", 0, 3)) {url = "http:" + url.substring(3);} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {url = "https:" + url.substring(4);}HttpUrl parsed = HttpUrl.parse(url);if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);return url(parsed);}
这个方法的作用,是对url进行的判断,转化。如果传进来的url字符串是一个无效的http,https URL,会出现IllegalArgumentException,为了避免这种情况,就出现了这个方法,通过调用HttpUrl.parse()方法来进行转化,如果是无效的http,https URL,会返回null,而不出现IllegalArgumentException。
通过上面的方法,把要请求的url设置进去之后,接下来就调用了client.newCall(request).enqueue(Callback)请求代码,我们看一下newCall(request)的源码:
@Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}
我们发现他是创建了一个RealCall对象,RealCall是实现Call接口,enqueue()方法是Call接口的方法,所以,运行enqueue()方法的是RealCall对象,所以我们来看RealCall对象里面的enqueue()方法。
okhttp里面的Call概念是一个将要被执行网络请求,可以被取消,只可以被执行一次。
@Override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);client.dispatcher().enqueue(new AsyncCall(responseCallback));}
这里我们看主要的代码client.dispatcher().enqueue(new AsyncCall(responseCallback)); 这里我们发现,继续调用了Dispatcher的enqueue()方法,此刻OKhttp里面很重要的一个类出现在我们面前了,那就是Dispatcher(调度分配器)。其实之前在OKhttpClient对象创建的时候,就有dispatcher的身影了,不过这次是比较明显的看到 。。。
Dispatcher (调度分配器)
Dispatcher是一个主要在异步请求时会用到的一个策略,我看有的文章说的比较绝对,说只有在异步请求才会用到,其实同步也有用到,只不过没异步所占的份额大罢了。
Dispatcher内部维护了一个线程池,用来对Call网络请求,进行处理。每一个dispatcher可以最大并发64个请求,每一台主机可以最大并发5个。我们从下面代码可以看到:
public final class Dispatcher {
private int maxRequests = 64;private int maxRequestsPerHost = 5;private @Nullable Runnable idleCallback;/** Executes calls. Created lazily. */private @Nullable ExecutorService executorService;/** 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<>();public Dispatcher(ExecutorService executorService) {this.executorService = executorService;}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;}
上面代码中还有一些通过名字就知道的变量,就不介绍了。我们主要看一下线程池的创键,这边在创建线程池的时候,没有用常用的四大线程池,他创建了一个核心线程数为0,最大线程数为Integer.MAX_VALUE,线程存活时间为1分钟,容器为SynchronizeQueue的这样一个线程池。
关于SynchronizeQueue的介绍可以看这里SynchronizeQueue内容
他这个线程池很像四大线程池中的newCacheThreadPool。适合处理请求频繁,高并发的任务。
这里的线程池,默认是这样的,如果有需求可以自己设置的。
Dispatcher如他名字的含义一样,调度。进他的源码可以看到,它里面的方法就是,执行请求,取消请求,结束请求等之类的调用,他的类里面的代码不多,大家可以自己进去看看,很容易理解。这里提一嘴,建议在看这篇文章的同学,采取边看文章,边看源码的方式来理解掌握。
回过头来继续看这句代码:
client.dispatcher().enqueue(new AsyncCall(responseCallback));
我们知道他就是调用Dispatcher里面的enqueue方法,我们来看,Dispatcher里面的enqueue方法。
synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);} }
runningAsyncCalls是一个存储正在进行异步请求的容器,这边对它进行了判断,如果runningAsyncCalls的size小于最大并发量64,并且每台主机的正在请求量小于5(maxRequestsPerHost),runningAsyncCalls就把这个异步请求添加进去,接着用通过调用线程池中的线程去执行这个AsyncCall,否者就用readyAsyncCalls容器去存储该请求。这段逻辑很好理解。我们现在看一下AsyncCall。看看这个AsyncCall是什么。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;AsyncCall(Callback responseCallback) {super("OkHttp %s", redactedUrl());this.responseCallback = responseCallback;}
这里我们发现了之前传过来的Callback回调,AsyncCall是RealCall的内部类。AsyncCall 继承NamedRunnable ,咦。。有Runnable,难到。。。我们接着看NamedRunnable
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();
}
果然~~,NameRunnable是实现了Runnable接口。是一个抽象类,在run方法中执行了execute()方法,execute方法是一个抽象方法。他的实现在NameRunnable子类中,AsyncCall是NameRunnable的子类。所以,我们就定位到了AsyncCall类中的关键方法execute()。
@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 {eventListener.callFailed(RealCall.this, e);responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}
看到这里,我们要知道,之前Dispatcher里面的线程池执行的任务就是上面的execute()方法。然后根据上面的源码我们发现,在这里获取到了Response,网络响应结果。并且对根据条件,处理了对Callback回调的响应。
client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {}});
之前我们在使用OKhttp进行一步请求的时候,传进去的Callback,结果回调,就是AsyncCall 里面execute()方法里面的Callback。
获取到Response的这行代码
Response response = getResponseWithInterceptorChain();
我们待会再看。我们接着看,发现finally语句块中执行的下面的语句:
client.dispatcher().finished(this);
调用了Dispatcher的finished方法。
/** Used by {@code AsyncCall#run} to signal completion. */void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);}private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");if (promoteCalls) promoteCalls();runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();}}
这里的promoteCalls参数,在异步请求的情况下,Boolean值为true,同步的话,值为false。我们看到finished方法里面有一个同步代码块,首先他移除了当前正在运行的call(请求都完成了,到这该结束),逻辑没毛病。然后获取正在执行的请求数量,如果数量==0,idleCallback不为零,就执行idleCallback的run方法。idleCallback默认为null的,用户有需要得设置了才有。咦,我们刚刚是不是错过了什么逻辑。对了,这行代码还没看呢。没事咱现在看。
if (promoteCalls) promoteCalls();
我们分析的是异步请求,所以promoteCalls是true。我们看promoteCalls()方法。
private void promoteCalls() {if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}
逻辑很简单,我就不细说了,就是把readyAsyncCalls准备执行里的请求放到runningAsyncCalls正在执行的容器里,并通过executorService().execute(call);来执行。
接下来我们来看之前被我们放在一边的获取Response的方法,代码如下:
Response response = getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();interceptors.addAll(client.interceptors());//注意1interceptors.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());//注意2}interceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);}
这里我们见到了OKhttp里面的很重要一个接口Interceptor(拦截器)。
Interceptor(拦截器)
interceptor是什么?他是用来对发出去的网络请求和对应响应的responses 进行(Observes)观察,(modifies)修改和potentially short-circuits(可能的短路?掉线?不好翻译,理解吧),也就是对Request和Response进行修改,处理的一个借口。通常,interceptor(拦截器)会对Request和Response的Header进行add,remove,transform等操作。
public interface Interceptor {Response intercept(Chain chain) throws IOException;interface Chain {Request request();Response proceed(Request request) throws IOException;......省略...
我们现在来看上面的getResponseWithInterceptorChain()方法。他首先创建了一个ArrayList集合把一些xxxInterceptor添加进去,然后创建了RealInterceptorChain,并执行chain.proceed(originalRequest);方法。这里先说一个结论就是interceptors容器里面的各式各样的interceptor拦截器会,按添加的顺序,逐个执行intercept()方法。这个结论后续会被证明。然后我们上面代码,有两个注意点,那两个地方的interceptor是开发者,根据需要选择是否设置的。设置后执行顺序就是add的顺序。
接着我们看RealInterceptorChain.proceed(originalRequest);方法。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();calls++;// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);Interceptor interceptor = interceptors.get(index);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;}
这里,我删除了一些非关键代码,方便我们浏览。刚开始,index和calls的值都是0,我们看到他创建了一个RealInterceptorChain类型的next对象,他的构造方法这里注意一下,他的构造方法传进去的参数,有一个index+1,也就是说这个RealInterceptorChain的index值+1了。变成了1 注意这里是新创建的next对象里面的index值加一了,并不是说现在在所的对象里面index加一,Interceptor interceptor = interceptors.get(index);在执行该语句时,index还是0,这里如果开发者通过调用OKhttpClient的addInterceptor(Interceptor interceptor)方法,设置了拦截器,则会先执行用户通过该方法设置的拦截器,否则就是执行RetryAndFollowUpInterceptor 拦截器。这个拦截器看名字我们很容易知道,他是失败重试和重定向的时候发挥作用的,这里我们先不细说了。本篇文章的主要目的是捋清流程。我们接着看,这里我们没有add 自定义的拦截器,所以就执行了RetryAndFollowUpInterceptor 拦截器的interceptor.intercept(next);方法,注意这里把next对象,穿进去了,还有要记得我们之前强调的,next对象里面的index的值加一了已经,变成了1了。
然后我们看RetryAndFollowUpInterceptor 拦截器的interceptor.intercept(next);方法。
@Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();RealInterceptorChain realChain = (RealInterceptorChain) chain;//注意1Call call = realChain.call();EventListener eventListener = realChain.eventListener();streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),call, eventListener, callStackTrace);int followUpCount = 0;Response priorResponse = null;while (true) {if (canceled) {streamAllocation.release();throw new IOException("Canceled");}Response response;boolean releaseConnection = true;try {response = realChain.proceed(request, streamAllocation, null, null);//注意2releaseConnection = false;
上面的代码,别的我们先不看,我们主要看我上面标注的注意1,和注意2处的代码。看注意1,这里传进来的chain就是next对象,然后注意2,那边realChain.proceed()方法,(⊙o⊙)哦~有点熟悉哦,我们进去看一下。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();calls++;// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);Interceptor interceptor = interceptors.get(index);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;}
发现又走了回来,注意,此时index的值不再是0了,此时变成了1,接着就是上面一样的逻辑,创建新的next对象,index加1,从interceptors容器中取出下一个interceptor拦截器,执行intercept(next)方法。正是这种递归式的调用,来吧interceptors集合里面的所有拦截器都跑一边,最后获取到Response对象。
总结
不自觉中说了这么多,之后的文章会对Interceptor的各个实现类,CacheControl等重要类进行剖析。最后上一张总结图: