最近学习了一下okhttp的源码,发现okhttp是真滴复杂。因为okhttp是一个网络请求库,它涉及了网络请求的方方面面,比如:http协议,socket通信,计算机网络,线程的调度等等。所以我们不扣具体的实现细节,只学习其中的原理和大概实现,okhttp的内容很多,所以分篇学习记录。
okhttp的基本使用
OkHttpClient.Builder builder=new OkHttpClient.Builder();
OkHttpClient okHttpClient = builder.build(); //构造 OkHttpClient
//构造get请求
Request request = new Request.Builder()
.get() //Method GET
.url("https://www.baidu.com")
.build(); //构造请求信息
//构造post请求
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
"json");
Request request = new Request.Builder()
.post(body) //Method POST
.url("https://www.baidu.com")
.build(); //构造请求信息
//发起同步请求
try {
Response response=okHttpClient.newCall(request).execute(); //发起同步请求
} catch (IOException e) {
e.printStackTrace();
}
//发起异步请求
okHttpClient.newCall(request)
.enqueue(new Callback() { //发起异步请求
@Override
public void onResponse(final Call call, final Response response) throws IOException {
//成功拿到响应
int code = response.code();
ResponseBody body = response.body();
String string = body.string();
Log.d("hx","code:"+code+"body:"+string);
}
@Override
public void onFailure(final Call call, final IOException e) {
e.printStackTrace();
}
});
通过基本的get,post,同步或异步请求示例,可以看出okhttp的请求流程:
- 1.构造OkhttpClient
- 2.构造请求信息Request(get或post)
- 3.执行请求(同步或异步)
请求流程非常简单,只有3步,这是okhttp帮我们封装好的。但具体的请求原理,内部怎么实现的呢?下面进入源码看看
Okhttpclient
通过流程我们知道,要请求首先要构造一个okhttpClient对象。看一下他的构造方法:
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
.....省略
}
okhttpClient对象的创建需要传一个Builder对象,它的无参构造函数其实还是调用带有Builder参数的构造函数。Builder是OkhttpClient的静态内部类,通过OkhttpClient的构造函数可以看到,okhttpClient对象在创建的时候需要初始化很多的网络请求配置,所以就采用了建造者模式,方便调用者使用链式调用。
看一下静态内部类Builder
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
......
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
....
}
可以看到,使用 OkhttpClient okhttpClient=new OkhttpCient()
这种方式创建的okhttpClient对象,会使用okhttp的默认配置。包括在前面的基本使用中使用的方式:
OkHttpClient.Builder builder=new OkHttpClient.Builder();
OkHttpClient okHttpClient = builder.build(); //构造 OkHttpClient
看一下Builder类的build();
public OkHttpClient build() {
return new OkHttpClient(this);
}
使用默认配置的话两者都一样可以创建okHttpClient对象。但是如果想自定义okhttp请求过程中的配置的话,就要通过builder.build()的方式创建。okhttp有很多的优点,其中之一就是请求配置可以高度定制:
图中列出的方法都是可以配置的属性,其实还有很多没列出来,有兴趣的自己去文档或源码吧
Request
Request代表着一个请求信息
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
通过它的成员可以看到,request包含的请求信息有:请求的url,请求方法method,请求头header,请求体requestBody等。request的构造函数里面需要传一个内部类对象builder,request可以通过其内部类对象builder修改其携带的请求信息
执行请求
看一下执行请求 okHttpClient.newCall(request)
的源码:
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
newCall(Request) 方法调用了 RealCall.newRealCall() 方法:
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.eventListener = client.eventListenerFactory().create(call);
return call;
}
okHttpClient.newCall(request)最终返回了一个RealCall对象。RealCall实现了Call接口,Call接口代表着一个准备被执行的请求任务:
public interface Call extends Cloneable {
//返回这个call初始化时传入的原始request
Request request();
//同步执行请求任务(立即执行),阻塞拿到响应
Response execute() throws IOException;
//异步执行请求任务(通过线程池提交执行)
void enqueue(Callback responseCallback);
//是否已执行完毕
boolean isExecuted();
//是否取消
boolean isCanceled();
okhttp3.Call clone();
interface Factory {
okhttp3.Call newCall(Request request);
}
}
可以看到前面okhttp用法示例中发起同步请求的方法:
//发起同步请求
Response response=okHttpClient.newCall(request).execute(); //发起同步请求
以及异步请求的方法:
okHttpClient.newCall(request) .enqueue(new Callback() {.... } //发起异步请求
都是在Call中定义的. 拿到了Call的实例,RealCall 对象后,就可以发起请求了。首先看一下同步请求:
同步请求
//RealCall.execute()
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//实际调用的是dispatch.execute()
client.dispatcher().executed(this);
//执行请求,拿到响应结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//结束请求
client.dispatcher().finished(this);
}
}
同步请求做了三步操作:
- 1.调用dispatcher的execute()
- 2.执行请求任务
- 3.结束请求
再看一下异步请求:
异步请求
// //RealCall.enqueue()
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//实际是调用dispatcher的enqueue()方法
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
很简单,只是调用dispatcher的enqueue().所以不管是同步请求还是异步请求,都会去调用Dispatcher的相关方法。Dispatcher是负责请求任务的调度,是比较重要的一个类,大概的看一下:
public final class Dispatcher {
//最大并发数,最多发起64个请求
private int maxRequests = 64;
//同一个host最多发起5个请求
private int maxRequestsPerHost = 5;
//空闲的任务
private @Nullable Runnable idleCallback;
//执行异步请求任务的线程池
private @Nullable ExecutorService executorService;
//等待的异步请求队列
final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在执行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在执行的同步请求队列
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;
}
....
}
由上面代码可以得出Dispatcher内部实现了懒加载的无边界限制的线程池。参数解析
- 0:核心线程数量,保持在线程池中的线程数量(即使已经空闲),为0代表线程空闲后不会保留,等待一段时间后停止。
- Integer.MAX_VALUE:表示线程池可以容纳最大线程数量
- TimeUnit.SECOND:当线程池中的线程数量大于核心线程时,空闲的线程就会等待60s才会被终止,如果小于,则会立刻停止。
- new SynchronousQueue<Runnable>():线程等待队列。同步队列,按序排队,先来先服务
- Util.threadFactory("OkHttp Dispatcher", false):线程工厂,直接创建一个名为OkHttp Dispatcher的非守护线程。
回到之前RealCall中的同步请求调用:client.dispatcher().executed(this)
:
//Dispatcher.executed
synchronized void executed(RealCall call) {
//只是把请求添加到正在执行的请求队列中
runningSyncCalls.add(call);
}
RealCall中的异步请求调用:client.dispatcher().enqueue(new AsyncCall(responseCallback))
,首先是创建了一个AsyncCall对象,然后传给了enqueue()。先看一下AsyncCall:
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//结束请求
client.dispatcher().finished(this);
}
}
}
那么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();
}
可以看到NamedRunnable 只是一个实现了runnable接口的抽象类而已,run方法执行的时候会执行execute()方法,AsyncCall继承了NamedRunnable,实现了excute()方法,在excute()中会去请求网络拿到响应,然后结束请求。
继续回到前面的RealCall中的异步请求调用:client.dispatcher().enqueue(new AsyncCall(responseCallback))
:
//Dispatcher.enqueue()
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到dispatcher().enqueue()
里面的操作:
1.先判断当前正在并发执行的请求数是否小于最大数64,并且同一个host的请求数是否小于最大值5
2.如果2个条件都满足直接把当前请求添加到正在执行的请求队列,然后通过线程池提交执行请求任务
3.如果条件不满足,则添加到等待的请求队列中
在第二步,线程池执行请求了,就会去执行AsyncCall的execute()里面的代码,去执行请求,拿到响应结果,然后结束。
对比同步请求和异步请求的调用结果,可以发现他们的请求流程基本是一样的:
- runningAsyncCalls.add(call) 都是先把当前请求Call添加到正在运行的请求队列中
- Response response = getResponseWithInterceptorChain() 执行请求,拿到响应结果
- client.dispatcher().finished(this) 结束请求
不同的是:
- 同步请求是直接添加到请求队列,去请求。而异步请求不会立即请求,会先判断当前正在运行的请求是否超过最大并发数以及同一host的请求数是否超过最大值5个,如果都不超过最大值,直接添加到请求队列,去请求。超过了,则添加到等待队列等待执行。
- 同步请求并没有创建工作线程去执行,而异步请求是在线程池创建的异步线程中执行的,包括Callback 回调也是。所以用异步请求拿到响应后,不能直接做UI的更新操作,因为不是在主线程。
结束请求
前面总结了同步请求或异步请求的基本请求流程,在请求的三步都是结束请求,那么结束请求具体做了什么操作呢?看一下RealCall里面无论是同步请求还是异步请求,结束请求调用client.dispatcher().finished(this)
的源码:
//dispatcher.finished(RealCall call)
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
同步或异步结束请求时都是调用这个代码,只是第三个参数不同,同步请求时false,异步请求时true.再看一下finished()
:
//dispatcher. finished(Deque<T> calls, T call, boolean promoteCalls)
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//从请求队列移除请求call
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为false,不会去调用promoteCalls()方法,如果是异步请求,promoteCalls为true,会去调用promoteCalls()将等待的请求调整为执行请求:
//dispatcher.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();
//同一host上的请求数小于5个,就将当前的请求从等待调整为执行
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
结束请求的基本操作就是从请求队列移除当前请求,然后根据是否是异步请求来调整请求的状态。如果是异步请求,就会去查看等待队列中的请求是否能够调整为执行请求,如果可以调整,就添加到执行请求队列并提交给线程池执行。
至此,一次网络请求的流程就结束了,再最后总结一下okhttp的基本请求过程:
1.通过OkhttpClient的内部类builder创建一个OkhttpClient对象
2.创建一个Request对象,通过Request对象封来装请求信息
3.通过OkhttpClient拿到Call接口的实现类对象RealCall,再根据Requset的请求信息去调用RealCall的同步或异步请求方法来完成网络请求