引用
okhttp问世以来,以其高度封装、定制、简洁的api调用获得广大使用者的喜爱,目前最流行的网络请求框架莫过于rxjava+retrofit+okhttp,如果你一直停留在使用的地步,那你永远可能只是大自然的搬运工了,为了了解这些架构设计的巧妙以及为何会如此受欢迎,只有通过源码来了解设计精髓,学习square出品,必属精品的代码设计思路,本篇先来了解一下okhttp3,本片所有源码是基于okhttp3.8.1,为了了解设计思路,所贴源码可能会有部分省略。
简单的使用
- 同步请求
OkHttpClient client = new OkHttpClient(); // 1.1 构建HttpClient对象,okhttp的门面或者外观对象
Request request = new Request.Builder().url("http://www.baidu.com")
.build(); //1.2 使用构建者模式创建一个包含请求参数的Request对象
try {
Call call = client.newCall(request);//1.3 call对象表示一个执行请求的实体对象,一个call代表一个请求
Response response = call.execute(); //1.4 执行同步请求
if (response.isSuccessful()) { // 根据服务器返回数据封装的Response对象,包含响应码、响应体等
System.out.println("成功");
}
} catch (IOException e) {
e.printStackTrace();
}
接下来对上面的步骤,一步一步解释然后跟踪源码:
*1.1 okhttpclient采用外观者模式、构建者模式,创建一个http请求的client,该对象包含一个Build对象,用来定制化创建你所需要的client对象;
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher(); // 由call代表的请求的分发器
protocols = DEFAULT_PROTOCOLS; // 默认的协议 http2 http1.1
connectionSpecs = DEFAULT_CONNECTION_SPECS; // 设置连接时支持的tls层协议以及不进行数据加密
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault(); // socket生产工厂
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool(); //连接池 支持多路复用
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
通过观察okhttpclient其实就是在配置全局发送请求中所需要的各种定制化的参数,并且持有各个参数引用对象。
1.2 根据Http协议创建的Request请求,Request这个类总共还没300行代码,内部使用好了嵌套的构建者模式来对请求参数进行设置,主要就是Header、url、method、requestbody等一些在进行http请求中符合http协议的参数:
1.3
client.newCall(request);
client根据request中的参数创建一个执行该请求的执行体call对象,一个call就代表一个请求** Call是一个接口规定了需要执行的几个行为,具体的实现类有RealCall和AyncCall:
public interface Call<T> extends Cloneable {
// 同步执行网络请求
Response<T> execute() throws IOException;
// 异步执行网络请求
void enqueue(Callback<T> callback);
// 判断该请求一否已经执行完成
boolean isExecuted();
// 取消该请求
void cancel();
// 克隆一个一模一样的call对象也就是一个请求
Call<T> clone();
// 获取封装该请求参数的request对象
Request request();
}
既然Call只是规定了这些执行的行为,那一个请求的执行必然是由其由其实现类来执行,这就是面向接口编程。client.newCall(request)
这一步就是创建一个call对象:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
// 包含三个参数 1 当前client 2 request对象 3 是不是web socket http支持使用socket实现长连接
return new RealCall(this, request, false /* for web socket */);
}
可以看到,这一步是创建了RealCall对象,该对象就是执行同步请求的真正对象。
*1.4 Response response = call.execute()
这一步就是由请求的执行体realCall来发起同步请求:
@Override public Response execute() throws IOException {
synchronized (this) {// 添加同步锁,判断该RealCall是否已经执行
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);//1.5
Response result = getResponseWithInterceptorChain();//1.6
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
*1.5 调用在创建client的时候就初始化的Dispatcher,Dispatcher是一个请求分发器,内部包含了三个队列数组和一个线程池:
Dispatcher结构
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 线程池
/** Ready async calls in the order they'll be run. */ 正在等待的异步任务队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ 正在执行的异步任务队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */ 正在执行的同步任务队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
// 创建一个线程池,核心线程为0,最大为Integer的最大值,空闲线程60s没任务线程自动销毁
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的execute的的方法是将realCall对象加入到runningSyncCalls的队列中,
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
*1.6 这一步执行前,先来说一下拦截器,okhttp对于网络请求采用用了一个类似AOP的的拦截器链,链式调用所有拦截器,最后执行请求返回response,而okhttp内置了5个拦截器。
-
RetryAndFollowUpInterceptor
在网络请求失败后进行重试
当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连 接 -
BridgeInteceptor
设置内容长度,内容编码
设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
添加cookie
设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤 -
CacheInterceptor
当网络请求有符合要求的Cache时直接返回Cache
当服务器返回内容有改变时更新当前cache
如果当前cache失效,删除 -
ConnectInterceptor
为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。 -
CallServerInterceptor
负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。
拦截器的总体执行流程如下:
拦截器执行流程这一步
Response result = getResponseWithInterceptorChain();
就是执行拦截器链,直到返回Response:
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);
}
通过责任链模式循环调用所有拦截器,每个拦截器可以根据Request和Reponse前后执行相应的逻辑。
以上分析的是同步请求,异步请求也是大同小异:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
跟踪到Dispatcher中:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
以上就是Dispatcher的enqueue函数,先判断是否异步请求队列长度大于线程池最大请求数,以及当前主机的请求数超过5个。如果没有将给异步call加入到异步线程队列,调用线程池执行该call,如果超了,将该异步call加入到异步等待队列,
AsynCall是RealCall内部类,继承于NameRunnable,NameRunable其实就是Runnable的子类,定义了一个execute方法,执行在run()方法中:
/**
* Runnable implementation which always sets its thread name.
*/
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();
}
将AsyncCall加入到线程池,既然AsyncCall是一个Runnnable,那么就是执行Async的execute方法:
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(); // 调用拦截器链,执行请求 返回response
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 {
responseCallback.onFailure(RealCall.this, e);// 失败回调
}
} finally {
client.dispatcher().finished(this); // 完成了请求
}
}
在一次网络请求不管成功失败,都会调用finally中的这行代码client.dispatcher().finished(this);
别问我为啥?跟踪一下这行代码:
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
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();
}
}
将异步call熊runningAsyncCalls队列中移除,然后 如果是异步请求就会执行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();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
其实就是查看线程池情况,然后从readAsyncCall是中获取等待的异步call执行,如此循环,直到所有的异步call执行完成,大体流程如下:
网络同步与异步请求
结语
本篇只是从一次简单的网络请求来跟踪源码,梳理的一个大致流程,其中对于每个拦截器只是说明作用,其实每个拦截器设计也很巧妙,比如CacheInterceptor采用了策略模式来对网络缓存和本地缓存进行相应的处理,缓存采用DiskLruCache来缓存。ConnnectInteceptor内置连接池采用多路复用技术减少了创建connection的花销等等。
网友评论