okhttp介绍
OkHttp是一个非常优秀的网络请求框架,已被谷歌加入到Android的源码中。目前比较流行的Retrofit也是默认使用OkHttp的。所以OkHttp的源码是一个不容错过的学习资源,学习源码之前,务必熟练使用这个框架,否则就是跟自己过不去。
okhttp优点
- 支持HTTP2/SPDY黑科技
- socket自动选择最好路线,并支持自动重连
- 拥有自动维护的socket连接池,减少握手次数
- 拥有队列线程池,轻松写并发
- 拥有Interceptors轻松处理请求与响应(比如透明GZIP压-
缩,LOGGING) - 实现基于Headers的缓存策略
至于为什么有这么多优点,各位看官老爷在下面的源码解析中慢慢体会吧!
okhttp简单用法
既然是网络框架,那么先来看看它的post和get请求吧。总的来说,分为三步:
- 实例化一个OkHttpClient 对象;
- 构造Request请求体;
- 发请求,同步调用okHttpClient.newCall(request).execute();异步调用 okHttpClient.newCall(request).enqueue(new Callback())。
- get(异步)请求
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- get(同步)请求
OkHttpClient okHttpClient = new OkHttpClient();
Response response = okHttpClient.newCall(request).execute();
String dataStr=response.body().string();
- post(异步)请求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody
.Builder()
.add("name", "张士超")
.add("password", "123456")
.build();
Request request = new Request
.Builder()
.url(URL)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- post(同步)请求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody
.Builder()
.add("name", "张士超")
.add("password", "123456")
.build();
Request request = new Request
.Builder()
.url(URL)
.post(requestBody)
.build();
try {
Response response = okHttpClient.newCall(request).execute();
String dataStr=response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
- 总结:post与get请求区别在于需要构造一个RequestBody对象,同步和异步区别在于执行的是execute()还是enqueue(new Callback()),这也很好理解,异步需要回调接口反馈的请求数据对吧。
okhttp的源码深入
- 好啦,步入正题,看看我们的okhttp同学是怎么样完成一个个请求的!
首先我们看看okhttp源码涉及到的几个类的源码:
- 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.
这里面比较重要的是url、method、header和RequestBody,分别代表request请求的请求URL、请求方法、请求头和请求体。
- Response响应类
public final class Response implements Closeable {
final Request request;
final Protocol protocol;
final int code;
final String message;
final @Nullable Handshake handshake;
final Headers headers;
final @Nullable ResponseBody body;
final @Nullable Response networkResponse;
final @Nullable Response cacheResponse;
final @Nullable Response priorResponse;
final long sentRequestAtMillis;
final long receivedResponseAtMillis;
这里面比较重要的是code响应码、message响应信息、headers响应头和body响应体。
- Okhttp类
final Dispatcher dispatcher;
final @Nullable Proxy proxy;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
final SocketFactory socketFactory;
final @Nullable SSLSocketFactory sslSocketFactory;
final @Nullable CertificateChainCleaner certificateChainCleaner;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
final ConnectionPool connectionPool;
final Dns dns;
final boolean followSslRedirects;
final boolean followRedirects;
final boolean retryOnConnectionFailure;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
Okhttp包含的东西很多,这里我们需要重点关注的是dispatcher调度器,interceptors自定义应用拦截器和networkInterceptors自定义网络拦截器。
-
ok,比较重要的三个类介绍完了,接下来我们看一看同步和异步请求的源码解析,先放一张图(盗的别人的图hhhh),让看官老爷门大致上有一个印象,后面比较好理解。
整个过程大致说一下吧,首先不管啥请求okHttpClient.newCall(request)这玩意实际上返回一个RealCall类,然后同步请求调用execute(),异步调用enqueue()之后给调度器,其实同步和异步都给了调度器,只是异步调用了调度器的execute()进行请求调度处理,下面给一张图就了解啦:
处理完之后通过一系列花里胡哨的拦截器之后返回一个response响应,就获得数据啦!
请求过程源码解析
以下面代码为例,看看源码都干了啥:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
okHttpClient.newCall(request).enqueue(new Callback()/execute()
前面两句话就不说了,就是OkHttpClient和Request 对象的实例化,我们看看okHttpClient.newCall(request)这里都干了些啥:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false );
}
进去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;
}
总之就是工厂模式将request封装成一个RealCall对象,接着看okHttpClient.newCall(request).execute(),同步请求;
@Override public Response execute() throws IOException {
synchronized (this) {//1
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);//2
Response result = getResponseWithInterceptorChain();//3
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);//3
}
}
比较重要的是上面三处注释:
- 首先看这个请求有没有被执行,每个请求只能被执行一次;
- 若没有被执行,则将这个请求任务给调度器dispatcher,简单看看这个调度器做了些什么:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
将这个请求加入到runningSyncCalls队列中,后面详细说说这个调度器。
- 经过一系列花里胡哨的拦截器得到响应,这具体后再说怎么做的。
- 执行完之后调度器dispatcher结束这个请求任务,看看具体怎么做的:
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
将请求任务从runningSyncCalls队列中移除。
-
这样整个同步请求流程就实现了,看下面这个图会清晰很多:
调度器
- 上面可以知道,请求处理都交给调度器了,那我们来看看这个调度器是个什么鬼:
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 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<>();
- 解释一下这几个参数啥子意思,第一个maxRequests最大请求数,表示最大并发数 ;maxRequestsPerHost 单个域名最大请求数 默认为 5 个。所以极端情况下,才会开启 64 个线程。这个场景非常罕见;readyAsyncCalls 表示异步请求的等待队列,runningAsyncCalls 正在执行的异步请求队列,AsyncCall队列;runningSyncCalls 正在执行的同步请求队列,因为是同步的,这也好理解,所以不需要等待队列。executorService这个就是调度器的线程池,java线程池我们都知道,我们看看这里的线程池怎么定义的:
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,非核心线程无数个,每个空线程的回收时间为60s。
异步请求
异步请求以下面这个为例,看看源码都怎么做的:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- 与同步请求的差距主要在于调用的是enqueue()函数,点进去看看与execute的区别
@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));
}
- 先判断有没有执行过,如果没有,这执行调度器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小于最大并发数,而且单域名的正在执行异步队列小于最大单域名最大请求数,则将这个请求放到正在执行的异步请求队列,并调用调度器的execute()方法,否则就放入等待异步队列。
- 调度器线程池总结
- 调度线程池Disptcher实现了高并发,低阻塞的实现
- 采用Deque作为缓存,先进先出的顺序执行
- 任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能
拦截器
- 主要是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);
return chain.proceed(originalRequest);
}
1)在配置 OkHttpClient 时设置的 interceptors;
2)负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
3)负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor;
4)负责读取缓存直接返回、更新缓存的 CacheInterceptor;
5)负责和服务器建立连接的 ConnectInterceptor;
6)配置 OkHttpClient 时设置的 networkInterceptors;
7)负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。
-
OkHttp的这种拦截器链采用的是责任链模式,这样的好处是将请求的发送和处理分开,并且可以动态添加中间的处理方实现对请求的处理、短路等操作。给个图加强理解:
- 不多说了,结合图和源码理解吧,创作不易,给个小心心吧!