OkHttp同步请求步骤:
- 创建OkHttpClient,客户对象
- 创建Request,请求主体,在请求主体设置请求的url,超时时间等
- 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的execute()发起同步请求。
- execute()返回的是Response对象。可以用execute().body().toString()得到请求所返回的主体内容。
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val response = client.newCall(request).execute().body().toString()
注意:发送请求后,就会进入阻塞状态,直到收到响应。
OkHttp异步请求步骤:
- 创建OkHttpClient,客户对象
- 创建Request,请求主体,在请求主体设置请求的url,超时时间等
- 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的enqueue()发起异步请求。
- enqueue(object: Callback{重写onFailure、onResponse方法}) 在onResponse方法中获取申请数据内容。
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val response = client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
response.body().toString()
}
})
源码分析:
注意:下面的源代码段可能来自不同一个类文件,只是将他们放一起,容易观察,主要放一些关键代码,其他会有...代替。
1.关于创建OkHttpClient对象,下面源码:
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
......
connectionPool = new ConnectionPool();
.....
}
可以看到OkHttp采用了建造者模式,在Builder()里面封装各种需要的属性,关键的主要有dispatcher分发器,connectionSpecs决定是异步还是同步,connectionPool 连接池。连接池具体到连接拦截器才会使用到,每个连接都会放入连接池中,由它进行管理。
总结:新建Client对象时,新建了一个分发器和一个连接池,还有一些属性的初始化。
2.创建Request对象时,源码:
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
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);
}
可以看到Request里面也有一个Builder类,Builder构造函数默认请求方式为Get,还有对请求头部的封装。
总结:新建一个Request对象里面主要封装了请求路径,头部信息等。
3.用newCall(request)将Reuqest对象封装成Call对象时,源码:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//可以看到newCall方法里面是调用了RealCall类的newRealCall方法,下面到RealCall类里看看。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// 调用RealCall的构造函数
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
//下面是RealCall类构造函数
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
......
}
总结:newCall方法实际生成RealCall对象,对象里面包含了Client客户对象和Request的请求对象,还新建了一个RetryAndFollowUpInterceptor 重定向拦截器。
4.Call对象调用的execute()同步请求方法,源码:
@Override public Response execute() throws IOException {
.....
//开启事件监听
eventListener.callStart(this);
try {
//分发器用executed方法将Call对象添加进同步运行队列
client.dispatcher().executed(this);
//结果是从拦截器链方法中获取的
Response result = getResponseWithInterceptorChain();
......
} finally {
//finish方法里将Call对象从Calls队列中移出
client.dispatcher().finished(this);
}
}
//下面进到client.dispatcher().executed(this)的excuted方法里面
synchronized void executed(RealCall call) {
//runningSyncCalls是正在运行的同步队列
runningSyncCalls.add(call);
}
总结:excute()同步申请方法,分发器将Call对象添加到同步运行队列。请求数据从Response result = getResponseWithInterceptorChain(); 中获取。
5.enqueue异步请求方法,源码:
@Override public void enqueue(Callback responseCallback) {
//判断是否请求过这个Call对象
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//异常检测
captureCallStackTrace();
//事件监听
eventListener.callStart(this);
//调用分发器的enqueue方法,分发器在client创建时新建的。
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到enqueue方法里面又调用了分发器的enqueue方法,在enqueue方法里新建了一个AsyncCall对象,
AsyncCall对象传入我们上一层传入enqueue方法的CallBack对象。
接下来看看上面的AsyncCall类是什么东西。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...........
}
看到AsyncCall继承自NamedRunnable,再来看看NamedRunnable是什么东西
public abstract class NamedRunnable implements Runnable {
.....
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
.....
}
可以看到NamedRunnable实现了Runnable接口,里面最核心的就是在run方法里面运行了execute()方法,这个方法的具体实现其实跟同步请求execute方法一样,在AsyncCall类里,和同步请求最后的execute()是同一个方法。
@Override protected void execute() {
.......
Response response = getResponseWithInterceptorChain();
.....
}
我把大部分代码都省了,最重要的就上面那句,跟同步请求一样,最后结果也是经过一系列拦截器的方法后的数据。
那么同步跟异步有什么区别呢?
异步传入enqueue方法的CallBack的对象实现了Runnable接口,让它在子线程中运行。
还有,接下来回到开头看看client.dispatcher().enqueue(new AsyncCall(responseCallback));这句,分发器类里的变量和它的enqueue方法(刚刚看的是AsyncCall类)。
public final class Dispatcher {
//默认的最大并发请求量
private int maxRequests = 64;
//单个host支持的最大并发量
private int maxRequestsPerHost = 5;
.........
//异步等待队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步运行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步运行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...........
//计算队列内请求数量的方法,如果异步请求满足不超过64,5的条件则进行请求操作。
//有的版本OkHttp是通过promoteAndExecute()进行条件判断,原理差不多
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//把Call对象添加进runningAsyncCalls异步进行队列
runningAsyncCalls.add(call);
//创建线程池并执行Call请求
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
......
}
分发器对Request类型进行判断,把Call对象添加进readyAsyncCalls异步等待队列或runningAsyncCalls,而在同步请求时分发器是把Call对象直接添加到runningSyncCalls同步运行队列。异步请求最后开启线程池获取数据。
总结:enqueue方法传入CallBack对象,CallBack对象被封装为AsyncCall,AsyncCall内部实现了Runnable接口,分发器进行判断,如果符合条件就把AsyncCall传入了异步进行对列,开启线程池在子线程获取数据。否则添加进异步等待队列。
readyAsyncCalls异步等待队列的请求什么时候能运行呢?
我们已经知道异步跟同步请求通过分发器分发队列,但是最后都要经过AsyncCall类的execute()方法来获取数据,execute()方法最后finally里面运行client.dispatcher().finished(this);方法,我们进去finished方法看看。
//异步请求的finished方法
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
//同步请求的finished方法
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
//具体的finished方法
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!");
//注意promoteCalls是第三个参数,既如果是异步请求才会运行该方法,重新整理队列。
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
进入promoteCalls()方法
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//循环到队列最后一个元素,call为最后一个请求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//如果符合条件就把call从等待队列移除加入运行队列。
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
总结:在readyAsyncCalls队列中的请求会在异步请求的finished方法里进行判断,如果符合条件则进入runningAsyncCalls。
Dispatcher分发器:
从上面的Dispatcher类可以看出分发器有3个队列,异步有两个队列是运用了消费者模式,类中还有excuted,enqueue,finished方法,Dispatcher主要作用就是根据Request类型将Call对象调入不同队列,最后用finished将完成的请求移除队列并把等待的请求调进运行队列。
ExecutorService线程池:
下面进到executorService().execute(call)看看,
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就代表线程空闲之后不会被保留,会被销毁;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。
第二个参数是int整数的最大值,他表示的是线程池中可以容纳的最大线程数量。但是确实它得满足64跟5的条件。
第三个keepAliveTime,当我们的线程池中线程数量大于核心线程数量时,空闲线程需要等待60秒的时间才会被终止。
OkHttp拦截器
我们已经知道了请求最后都是从Response result = getResponseWithInterceptorChain()这句中获取的数据。
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
总归创建了6个拦截器
全部拦截器的基本流程:
- 在发起请求前对request进行处理。
- 调用chain.proceed()方法,获取下一个拦截器的response。
- 对reponse进行处理,返回给上一个拦截器。
RetryAndFollowUpInterceptor 重定向拦截器
负责失败重连的拦截器。
- 创建StreamAllocation对象,但是没有使用,它的使用是在ConnectInterceptor。它负责为一次“请求”寻找“连接”并建立“流”。Connection是建立在Socket之上的物理通信信道,而Stream则是代表逻辑的流,如果有多个stream(即多个 Request) 都是连接在一个 host 和 port上,那么它们就可以共同使用同一个 socket ,这样做的好处就是可以减少TCP的一个三次握手的时间。
- 调用RealInterceptorChain.proceed(...)进行网络请求
- 根据异常结果或响应结果判断是否进行重新请求(20次)
- 调用下一个拦截器,对response进行处理,返回上一个拦截器
BridgeInterceptor 桥拦截器
该拦截器是链接客户端代码和网络代码的桥梁,它首先将客户端构建的Request对象信息构建成真正的网络请求;然后发起网络请求,最后就是讲服务器返回的消息封装成一个Response对象。
- 将用户构建的Request请求转化为能够进行网络访问的请求
- 执行符合条件的请求
- 将Response转化为用户可用的Response,OkHttp支持Gzip压缩,GzipSource类对数据进行解压。
CacheInterceptor 缓存拦截器
该拦截器用于处理缓存的功能,主要取得缓存 response 返回并刷新缓存。
- 底层使用的是 DiskLruCache 缓存机制。
- CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
- get() 方法获取一个 CacheStrategy 对象。CacheStrategy,它是一个策略器,负责判断是使用缓存还是请求网络获取新的数据。
- responseCache.put(userResponse);
- put(userResponse)方法将 userResponse 缓存到本地。
为什么需要缓存 Response?
- 客户端缓存就是为了下次请求时节省请求时间,可以更快的展示数据。
- OKHTTP 支持缓存的功能
ConnectInterceptor 连接拦截器
该拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpCodec它包括通向服务器的输入流和输出流。
- 获取到第一个拦截器生成的StreamAllocation对象,
- 通过StreamAllocation对象,streamAllocation.newStream()创建HttpCodec对象,
- streamAllocation.connection()获取一个RealConnection对象
- 将HttpCodec、RealConnection对象传递给拦截器
NetworkInterceptors 网络拦截器
- 允许像重定向和重试一样操作中间响应。
- 网络发生短路时不调用缓存响应。
- 在数据被传递到网络时观察数据。
- 有权获得装载请求的连接。
CallServerInterceptor 调用服务拦截器
该拦截器的功能使用 HttpCodec与服务器进行数据的读写操作的。
- 首先是获取了httpCodec对象,该对象的主要功能就是对不同http协议(http1.1和http/2)的请求和响应做处理,该对象的初始化是在ConnectIntercepor的intercept里面
- OkHttp通过OKIO的Sink对象(该对象可以看做Socket的OutputStream对象)的writeRequest来向服务器发送请求的。
- 将OKIO的Source对象作为输入流InputStream对象读取数据封装为Response对象。
- 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。
OKHTTP的责任链模式优点:
- 可以降低逻辑的耦合,相互独立的逻辑写到自己的拦截器中,也无需关注其它拦截器所做的事情。
- 扩展性强,可以添加新的拦截器。
当然它也有缺点:
- 因为调用链路长,而且存在嵌套,遇到问题排查其它比较麻烦。
ConnectionPool
OkHttp中所有的连接(RealConnection)都是通过ConnectionPool来管理。
- StreamAllocation里面包含了RealConnection对象,该对象归根是由ConnectionPool的get() 方法遍历 connections 中的所有 RealConnection 寻找同时满足条件的RealConnection,重复利用RealConnection。
- ConnectionPool类里put方法,采用GC回收算法,异步触发清理任务,然后将健康的connection添加到connections队列中。调用cleanup方法执行清理,并等待一段时间,持续清理,其中cleanup方法返回的值来来决定而等待的时间长度。