序言
上一篇文章介绍了Retrofit的原理,今天,我们就来探究一下OkHttp的原理。Retrofit是对OkHttp做封装,真正发送网络请求的就是OkHttp框架。
使用Retrofit框架过程中,对OkHttp最直观的认识就是OkHttpClient,这篇文章会按如下目录展开介绍:
- OkHttp的用法
- OkHttp在Retrofit中的使用
- OkHttp的原理
- 对比OkHttp与其他网络库
- 总结
OkHttp的用法
本文基于OkHttp3.10.0
版本进行介绍。
- 首先添加OkHttp依赖:
implementation("com.squareup.okhttp3:okhttp:3.10.0")
如果项目中添加了Retrofit2依赖,就不用再添加OkHttp3的依赖,因为Retrofit2库使用到了OkHttp3,并且已经添加了OkHttp3的依赖,所以不用我们重复添加。
- 使用方法:
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
//创建Request请求对象
Request request = new Request.Builder()
.url(url)
.build();
//创建Call对象,并执行同步获取网络数据
Response response = client.newCall(request).execute();
return response.body().string();
}
总结一下,OkHttp的使用步骤如下:
- 创建OkHttpClient对象
- 创建Request对象
- 创建Call对象
- 通过Call对象发起请求,同步请求调用call.execute方法,异步请求调用call.enqueue方法
OkHttp在Retrofit中的使用
结合之前的一篇文章Retrofit源码解析,我们来看一下在Retrofit中怎样使用OkHttp。
- 创建OkHttpClient对象
在初始化Retrofit对象的时候,我们会传入一个OkHttpClient对象:
DeviceRetrofit() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(Config.HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new DeviceInterceptor())
.addInterceptor(OkHttpUtils.getHttpInterceptor(TAG))
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(Config.DEVICE_HOST)
.addConverterFactory(JacksonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
mService = retrofit.create(DeviceService.class);
}
这里完成了第一步,创建OkHttpClient对象。
- 创建Request对象
当我们通过Retrofit发起一个请求的时候,在ServiceMethod中会创建一个okhttp3.Request对象:
/**
* 创建Call对象
*/
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
/**
* 创建Request对象
*/
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler
这里完成了第二步,创建了一个Request对象。
- 创建Call对象
第二步的代码中,有一个createRawCall方法,它会从callFactory工厂中创建一个Call对象,之前说过,callFactory就是OkHttpClient,所以看OkHttpClient的newCall方法:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
这里完成了第三步,创建了一个RealCall对象。
- 调用Call的execute方法:
在RxJavaCallAdapterFactory.CallOnSubscribe.call方法中,会调用call.execute()发送同步请求:
static final class CallOnSubscribe implements Observable.OnSubscribe> {
private final Call originalCall;
CallOnSubscribe(Call originalCall) {
this.originalCall = originalCall;
}
@Override public void call(final Subscriber super Response> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
final Call call = originalCall.clone();
// Attempt to cancel the call if it is still in-flight on unsubscription.
subscriber.add(Subscriptions.create(new Action0() {
@Override public void call() {
call.cancel();
}
}));
try {
Response response = call.execute();
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(response);
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (!subscriber.isUnsubscribed()) {
subscriber.onError(t);
}
return;
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
}
这里就完成了第四步,调用call.execute方法。
可见,Retrofit中确实是按照OkHttp3的四个步骤进行使用。
OkHttp的原理
上面介绍了OkHttp的使用方法,以及Retrofit中怎样使用OkHttp。那么下面我们就来分析一下OkHttp的原理,OkHttp是怎样发送Http请求的?我们还是按照这四个步骤进行分析。
- 创建OkHttpClient对象
我们看一下OkHttpClient的实现:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
static final List DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.HTTP_1_1);
static final List DEFAULT_CONNECTION_SPECS = Util.immutableList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
final Dispatcher dispatcher; //请求分发器
final @Nullable Proxy proxy;
final List protocols; //支持的协议,默认http1.1和http2
final List connectionSpecs;
final List interceptors; //用户设置的拦截器
final List networkInterceptors;
final EventListener.Factory eventListenerFactory; //请求事件监听器工厂
final ProxySelector proxySelector;
final CookieJar cookieJar; //可设置往请求头中加入cookie信息
final @Nullable Cache cache; //可设置是否缓存请求Response策略
final @Nullable InternalCache internalCache; //OkHttp3.0之后使用cache,不再使用此成员变量
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; //默认连接超时时间10s
final int readTimeout; //默认读取超时时间10s
final int writeTimeout; //默认写入超时时间10s
final int pingInterval;
//省略其他代码
}
部分关键的成员标了注释,各位同学可以看一下。着重关注一下Dispatcher类,它是网络请求分发器,同步请求和异步请求会做不同的分发处理,后面会详细介绍该类。
- 创建Request对象
看一下Request的实现:
/**
* An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
* immutable.
*/
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;
}
//省略其他代码
}
从注释中可以知道,这个类代表Http请求,其中包含了Http请求所需的信息。
-
url
:请求地址 -
methdo
:请求类型,如GET、POST、DELETE、PATCH、PUT等 -
headers
:请求头信息 -
body
:请求体 -
tag
:标签
我们在看一下Header类的结构:
public final class Headers {
private final String[] namesAndValues;
Headers(Builder builder) {
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
}
//省略其他代码
}
可以看到Headers类中通过一个字符串数组保存头信息,保存方式如下{key1, value1, key2, value2, ...}
再看一下RequestBody类的结构:
public abstract class RequestBody {
/** Returns the Content-Type header for this body. */
public abstract @Nullable MediaType contentType();
/**
* Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo},
* or -1 if that count is unknown.
*/
public long contentLength() throws IOException {
return -1;
}
/** Writes the content of this request to {@code sink}. */
public abstract void writeTo(BufferedSink sink) throws IOException;
//省略其他代码
}
RequestBody是一个抽象类,其中有两个抽象方法:
-
writeTo方法
:用于把请求体的内容写入到sink(发送请求到服务器的对象)中 -
contentType方法
:标志请求体内容的类型。
Request相关内容介绍完毕,接下来看一下创建Call部分。
- 创建Call对象
Call对象,我们可以理解为一次网络请求的封装,一个Call对象只能被执行一次。那么,是如果保证只能被执行一次的特性呢?
上面提到过,Retrofit会为一次请求创建一个RealCall对象:
final class RealCall implements Call {
final OkHttpClient client;
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
/**
* There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward.
* This will be set after we create the call instance then create the event listener instance.
*/
private EventListener eventListener;
/** The application's original request unadulterated by redirects or auth headers. */
final Request originalRequest;
final boolean forWebSocket;
// Guarded by this.
private boolean executed;
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
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);
}
}
@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));
}
//省略其他代码
}
我们可以看到,RealCall中有个布尔型的变量executed
,标志该Call是否已经执行了。
在execute和enqueue两个方法中都会校验executed,这就保证了一个Call只能执行一次。
- 调用call.execute或call.enqueue
再看execute和enqueue两个方法,都会调用Dispatcher对应的方法。那就看看Dispatcher的实现:
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 readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
//省略其他代码
}
Dispatcher中有几个成员变量,依次介绍一下:
-
maxRequests
:表示异步请求支持的最大并发请求数 -
maxRequestsPerHost
:表示每个Host支持的最大并发请求数,Host可以理解为baseUrl -
idleCallback
:分发器闲置回调,即分发器中没有正在执行的请求和准备执行的请求,就会回调idleCallback.run方法 -
executorService
:线程池,用于管理异步请求的线程 -
readyAsyncCalls
:处于准备状态的异步请求队列 -
runningAsyncCalls
:正在执行的异步请求队列 -
runningSyncCalls
:正在执行的同步请求队列
着重关注两个异步请求队列,什么时候会进入异步准备队列,什么时候又会进入异步执行队列呢?
runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost
满足上面条件,即异步线程数量已经超过最大请求数,或者单个Host的异步请求数超过最大请求数,就会进入异步准备队列,否则直接进入异步执行队列。
我们再看Dispatcher的executed和enqueue方法,发现executed方法很简单:
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
这里只是把同步请求加入同步执行队列中,并没有具体的请求执行操作。那么这个执行操作在哪里呢?
我们看回RealCall的execute方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
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);
}
}
在调用了Dispatcher.executed方法后,会调用getResponseWithInterceptorChain()
方法,那么可以肯定,这个方法就是真正执行请求的地方。
再看一下异步请求,RealCall的enqueue方法中,调用Dispatcher.enqueue方法时,会创建一个AsyncCall对象作为参数传入。那我们看一下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);
}
}
}
在AsyncCall的execute方法中,也会调用getResponseWithInterceptorChain()
方法。
也就是说,不管是同步还是异步请求,都是通过getResponseWithInterceptorChain()方法真正执行请求的。
接下来,当然是看getResponseWithInterceptorChain方法的实现:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List 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);
}
这个方法中,先得到一个interceptors列表,其中包含的元素有:
用户设置的interceptor列表
-
RetryAndFollowUpInterceptor
:错误重试和重定向拦截器 -
BridgeInterceptor
:把用户的request转换为Http的request,把Http的response转换为用户需要response的转换桥拦截器 -
CacheInterceptor
:处理Response缓存的拦截器 -
ConnectInterceptor
:建立服务器连接的拦截器 用户设置的networkInterceptors列表
-
CallServerInterceptor
:真正向服务器发送请求并且得到响应的拦截器
然后创建一个RealInterceptorChain对象,继而调用其proceed方法。
RealInterceptorChain实现了Interceptor.Chain接口,先看一下接口定义:
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@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);
}
}
再看RealInterceptorChain的定义:
public final class RealInterceptorChain implements Interceptor.Chain {
private final List 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;
public RealInterceptorChain(List interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
//省略部分代码
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 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 next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// 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;
}
}
我们着重看proceed方法,proceed方法我用一张流程图来总结:
通过这张图,可以很清晰的看到执行的流程:
通过RealInterceptorChain一层一层调用各interceptor的intercept方法,并且在此过程中可以处理request,比如往其中添加参数,或者做一些其他操作。一直到CallServerInterceptor,它是最后一个拦截器,真正用来执行网络请求,从服务器获取Response。然后把Response一层一层网上传递,在此过程中,可以对Response做一定的处理。
这个过程用到了责任链模式,以链式调用,每一个节点可以执行不同的逻辑,各节点之间解耦,扩展性强,可以随意添加节点。
想知道怎样执行网络请求的同学,可以自行研究各个Interceptor,尤其是CallServerInterceptor,它是真正执行网络请求的地方。主要是调用了Http1Codec.flushRequest方法,继而调用BufferedSink.flush方法,之后的就留给各位同学自己去看了。
最后得到的Response对象交给Retrofit进行转换适配,到这里OkHttp的原理分析完毕。
对比OkHttp与其他网络库
这里主要对比几个常用的网络扩,包括android-async-http、volley、OkHttp和Retrofit。
- android-async-http(loopj)
- 基于HttpClient
- 自动请求重试
- 持久化cookie,保存在sp中
- 作者已经停止对项目维护,因此不推荐在项目中使用
- volley(Google)
- 基于HttpURLConnection
- 封装图片框架,支持图片加载
- 与Activity生命周期的联动,Activit结束时取消所有的网络请求
- 适合数据量小,频率高的请求,不适合上传或下载大文件,原因:线程池默认大小是4,Request.getBody()返回的是字节数组,也就是对于Post或Put请求,会把传输的数据一股脑读到内存中,很容易出现oom
- OkHttp(Square)
- 不基于HttpClient和HttpURLConnection,本身可以理解为一个封装之后的HttpURLConnection
- 集各种优点与一身
- Android4.4源码可以看到HttpURLConnection已经替换为OkHttp实现,可见其强大
- Retrofit(Square)
- 基于OkHttp
- RESTful Api设计风格
- 通过注解配置请求
- 支持同步、异步
- 易与其他框架配合使用,如Rxjava
- 使用方法较多,原理比较复杂,存在一定的门槛
- 高度解耦,扩展性极好,堪称代码设计的典范
总结
相信看到这里,各位同学对OkHttp有了一个深刻的认识,再结合Retrofit那篇文章看,就更能前后贯通理解整个Retrofit+OkHttp的流程。