网络基础知识:okhttp是一个网络框架,帮我们封装了网络请求中需要重复做的事。
发起网络请求一般都是使用http协议,需要提供一下内容:
-
请求方法GET、POST、PUT、DELETE、HEAD等(常用的请求方法是get和post,而put一般是用来做更新,post一般是用来做提交的)
-
请求资源地址url
-
使用的http协议版本http/1/1.1/2
-
多个请求header
-
回车,换行符
-
请求body数据
有了发起http请求的主要信息,我们在网络请求时只要提供url和请求实体,剩下的就是网络框架帮我们做了。发起请求,一般使用socket,具体实现可能通过tcp或者udp来传输,tcp的话需要建立连接,这过程需要写很多代码,网络框架就要帮我们处理了。
服务端处理请求后,给我们发回响应,包括响应码,响应头和响应体。
常见的响应码:
-
200 请求成功
-
304 缓存可以继续用
-
404 地址有错误
-
500 服务器内部错误
发起请求及响应的过程如下:
-
域名解析,向DNS服务器发出域名解析请求,获取该域名对应的IP地址
-
根据IP发起TCP/IP三次握手连接
-
连接成功,构造http请求信息
-
发送请求
-
服务端收到请求,进行处理
-
服务端将响应已经建立的连接,向客户端发送数据
-
客户端收到响应进行处理
在请求过程中,第一步域名解析是操作系统底层实现的,从第二步开始就要我们自己实现了。我们最好只做业务相关的工作:发起请求
和 拿到响应
。
需要网络框架帮我们把重复的操作都封装好:
-
剩余的建立连接
-
构造请求信息
-
发起请求
-
接收请求
-
请求响应码和请求头的基本处理
深入了解okhttp
OkHttp 请求实现流程:
使用OkHttp
发起一个异步请求比较简单:
-
需要构造一个
OkHttpClient
-
构造请求信息
Request
,并将Request
封装成Call对象 -
发起请求(同步调用的是Call对象的
execute
方法进行请求,异步调用的是Call对象的enqueue
方法请求)
代码如下:
private void testOkHttp() {
OkHttpClient okHttpClient = getOkHttpClient(); //构造 OkHttpClient
Request request = new Request.Builder()
.get() //Method GET
.url("www.baidu.com")
.build(); //构造请求信息
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();
}
@Override
public void onFailure(final Call call, final IOException e) {
e.printStackTrace();
}
});
}
注:异步请求的回调Callback中的onResponse和onFailure方法都是在子线程中执行的
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) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
RealCall.newRealCall()
方法创建了一个新的RealCall
对象,这个RealCall
是okhttp3.Call
接口的一个实现。
okhttp3.Call
表示一个等待执行的请求,它只能被执行一次,定义了这些方法:
public interface Call extends Cloneable {
//返回这个请求关联的 Request 对象
Request request();
//立即执行请求,阻塞等待拿到响应
Response execute() throws IOException;
//请求入队,异步执行
void enqueue(Callback responseCallback);
//取消一个请求
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
可以看到,我们前面发起异步请求的 enqueue()
方法是定义在 Call
中的。
okHttpClient.newCall(request)
.enqueue(new Callback() { ...}); //原来就是 Call 的方法
在 OkHttp 中,Call
的唯一实现就是 RealCall
,它表示一个准备好被执行的请求。和 Request
不同在于,它还提供了发起请求、取消等方法。
拿到 OkHttp.Call
的实例RealCall
对象后,我们调用了它的 enqueue()
方法:
//RealCall.enqueue()
@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));
,它做了两件事:
-
创建一个
AsyncCall
对象 -
调用
Dispatcher.enqueue()
方法将请求入队
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() {
//...
}
}
AsyncCall
就是一个 Runnable,用于异步执行任务。
接着看 client.dispatcher()
方法,它返回一个调度器 Dispatcher
,这是 OkHttp 中比较核心的一个类:
public final class Dispatcher {
private int maxRequests = 64; //同时最多发起 64 个请求
private int maxRequestsPerHost = 5; //同一 host 最多发起 5 个请求
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService; //将会异步创建的线程池
//等待被执行的异步请求队列
private 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;
}
//...
}
我们可以得到比较关键的信息如下:
-
在一个
OkHttpClient
中一般只有一个Dispatcher
,因此一个OkHttpClient
能发起的最多请求就是Dispatcher
中定义的 64 个 -
同样,同一 host 能发起的最多请求是 5 个
-
Dispatcher
中用三个队列保存同步、异步请求 -
默认线程池核心线程数量为 0,最多数量不限制,消息队列为
SynchronousQueue
,因此有请求时会不断创建新线程
然后回到我们之前调用的 Dispatcher.enqueue()
方法:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到,调度器在收到一个异步请求后,会先判断当前正在运行的异步请求是否超过默认的 64 个、同一 host 的请求是否小于默认的 5,是的话就开始执行;否则加入等待执行的队列中。
前面介绍了 AsyncCall
是一个 NamedRunnable
,等它被执行时会调用它的 run()
方法,这个方法调用了 execute()
方法。
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);
}
}
protected abstract void execute();
}
一个请求从提交到执行背后所经历的流程,如下图:
image一个异步请求在发起到执行要经历这么几个状态:
-
创建一个
Request
:代表用户提交的一个请求的基本信息,包括:URL,请求方法,请求头,请求体,自定义的CacheControl
,tag 等 -
创建一个
RealCall
:代表准备好被执行的一个请求,在Request
基础上添加了同步执行、异步执行、取消等操作 -
创建一个
AsyncCall
:代表可以异步执行的任务 -
添加到正在运行/等待运行的异步队列中
-
被执行后调用
AsyncCall.execute()
方法
了解请求发起的流程后,接着看后半部分:如何拿到响应
。
响应是如何拿到的
前面提到,请求加入队列后,被执行会调用 AsyncCall.execute()
方法,正是在这个方法里调用了我们设置的回调,将结果传给我们:
//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) {
//...
} finally {
client.dispatcher().finished(this); //从 dispatcher 里移除当前这个,执行下一个 ①
}
}
这个方法中先通过 getResponseWithInterceptorChain()
方法拿到了响应 Response
,然后进行了回调,最后在 finally
代码块中调用了 client.dispatcher().finished(this)
方法,这个方法的作用是从调度器 Dispatcher 里移除当前这个请求,执行下一个。
我们重点看 getResponseWithInterceptorChain()
方法,这个方法是 OkHttp
的核心。
//RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
// 把我们传入的和 OkHttp 内置的拦截器添加到一个列表里
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors()); //我们自定义的
//内置的 5 个拦截器
interceptors.add(retryAndFollowUpInterceptor); //重试、取消
interceptors.add(new BridgeInterceptor(client.coJar())); //桥接
interceptors.add(new CacheInterceptor(client.internalCache())); //缓存
interceptors.add(new CotInterceptor(client)); //连接
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket)); //最后是网络请求
//创建一个 RealInterceptorChain ,传入刚才的拦截器列表
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//开始执行
return chain.proceed(originalRequest);
}
可以看到,在这个方法里,主要做了 3 件事:
-
把我们构造
OkHttpClient
时传入的拦截器和OkHttp
内置的 5 个拦截器添加到一个列表里 -
创建一个
RealInterceptorChain
,传入刚才的拦截器列表 -
调用
RealInterceptorChain.proceed()
方法,这个方法会返回响应
这里提到了 OkHttp
大名鼎鼎的拦截器和拦截器链。很多人觉得 OkHttp
好用的理由之一就是它可以使用拦截器方便的修改请求和响应值,一般我们都会自定义拦截器用来添加请求 Header,或者打印请求、响应信息。
我们来看看这个拦截器链是怎么运转起来的吧。
首先看看 OkHttp
的拦截器和链接口:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
//当前的请求信息
Request request();
//处理请求,返回结果
Response proceed(Request request) throws IOException;
//执行当前请求的连接
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
可以看到,拦截器链 Chain
接口中定义了获取当前请求信息和处理请求的方法,然后还定义了一些修改超时时间相隔的方法,比价简单。
拦截器 Interceptor
接口则只有一个方法:
Response intercept(Chain chain)
这个方法的参数是一个拦截器链,返回一个响应。
一般我们自定义一个拦截器时,主要分三步:
-
修改请求信息
-
调用拦截器链获取结果
-
修改响应
比如:
public class MyInterceptor implements Interceptor {
@Override
public Response intercept(final Chain chain) throws IOException {
Request originalRequest = chain.request();
//修改请求
Request newRequest = originalRequest.newBuilder()
.addHeader("xx", "xx")
.tag("tag")
.build();
//调用拦截器处理获取结果
Response originalResponse = chain.proceed(newRequest);
//拿到结果进行处理后返回
Response newResponse = originalResponse.newBuilder()
.removeHeader("xx")
.build();
return newResponse;
}
}
其中最关键的是 Response originalResponse = chain.proceed(newRequest);
,我们去看看 OkHttp 中 Chain
唯一的实现类 RealInterceptorChain
怎么实现的吧。
首先看下 RealInterceptorChain
的成员属性:
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors; //所有拦截器
private final StreamAllocation streamAllocation; //连接引用相关
private final HttpCodec httpCodec; //底层数据流
private final RealConnection connection; //连接信息
private final int index; //当前拦截器的索引
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
//...
}
可以看到,有很多看起来很厉害的类,先别急,StreamAllocation
, HttpCodec
, RealConnection
我们后面介绍,这里我们只关心两个属性:
private final List<Interceptor> interceptors; //所有拦截器
private final int index; //当前拦截器的索引
还记得在前面提到的 RealCall.getResponseWithInterceptorChain()
方法吗:
//RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
//...
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
在这个方法里,构造了一个 RealInterceptorChain
,同时传入的参数 index 为 0,然后调用了它的 proceed()
方法:
//RealInterceptorChain.proceed()
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//...
// 先创建一个新的链,索引是当前加一
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); //它执行的参数是持有下一个引用的链
//...
return response;
}
在 RealInterceptorChain.proceed()
中我们看到,除了做一些异常判断,最重要的是上面我截取的部分,做了三件事:
-
先创建一个新的链,索引是当前加一(我们姑且叫它“下一个链”)
-
根据当前索引从所有拦截器中取出拦截器
-
调用拦截器的拦截方法,参数是下一个链
看到这里可能会有些懵,这是什么,调用了一个链的处理方法,结果在它里面又给我创建了一个链,不明白。
文字、代码比较抽象,看下图:
enter image description here首先我们从前面的 RealCall.getResponseWithInterceptorChain()
方法可以知道,OkHttp
首先创建了一个 RealInterceptorChain
,索引值是 0,这个 RealInterceptorChain
对象的 proceed()
方法会创建一个新的 RealInterceptorChain
,索引是 1(我们叫它“第二个链”),然后取出拦截器列表中的第一个拦截器,把第二个链作为参数调用拦截器的 intercept(Chain)
方法。
这时第一个拦截器的拦截方法就被调用了,参数是第二个链,它在处理请求信息后,调用拦截器链的 proceed()
方法,这个方法会再创建一个链(第三个),然后取出第二个拦截器然后调用它的拦截方法(参数是第三个链)。
以此循环,直到索引 index 超出拦截器列表的长度,就不再往下递归调用了,逐层返回结果。
刘望舒大神的这张图看着更直观:
enter image description here小结
我们对 OkHttp
如何发起请求、逐层处理、拿到响应有了一个基本的认识,概括如下:
-
发起异步请求后会构造异步任务
AsyncCall
入队,等被执行时会调用它的execute()
方法 -
这个执行方法会通过拦截器链,挨个调用我们自定义的和系统内置的 5 个拦截器,对请求信息和响应做处理,最后返回结果,回调我们传入的参数
-
然后从队列中移除当前任务,执行下一个,以此循环
okhttp拦截器总结
-
在发起请求前对request进行处理
-
调用下一个拦截器,获取response
-
对response进行处理,返回给上一个拦截器
okhttp缓存策略(使用的是DiskLruCache)
ConnectInterceptor(连接拦截器,负责建立连接和流对象的)
-
ConnectInterceptor获取Interceptor传过来的SteamAllocation,然后执行SteamAllocation.newStream(),创建RealConnection对象
-
选择不同的连接方式,根据是否需要隧道连接来选择隧道连接或者Socket连接
-
将刚才创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec(可以编码request,解码response)等对象传递给后面的CallServerInterceptor拦截器
ConnectionPool总结
-
okhttp使用了gc回收算法
-
SteamAllocation的数量会渐渐变成0
-
变成0后会被线程池检测到并回收,这样就可以保持多个健康的keep-alive连接
网友评论