OkHttp的同步与异步请求
- 同步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response = client.newCall(request).execute();
如果在安卓平台编码的话,记得要运行在子线程里面哦。
- 异步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("OkHttp3", "Call Failed:" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("OkHttp3", "Call succeeded:" + response.message());
}
});
这样的写法在开发中已经遇不到了,各种框架已经把这些操作封装进去了。只不过,我既然是回顾,那么就从原始一点的来。点进去稍稍看看源码,同步与异步在实现的方式上有啥不同。
- Call
首先我们会调用newCall()
方法。
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
准备在将来某个时候执行网络服务。
然后点击execute()
或enqueue()
都会调到Call
这个接口。
public interface Call extends Cloneable {
//返回发起此调用的原始请求。
Request request();
//立即调用请求,并阻塞直到响应可以处理了或正在处理
//错误。直接获取当前的返回数据Response。使用完毕之后,记得关闭ResponseBody
Response execute() throws IOException;
//异步执行
void enqueue(Callback responseCallback);
...省略代码
//取消请求。
//已经完成的请求不能被取消。
void cancel();
//创建一个新的、相同的调用,它可以被排队或执行,即使这个调用已经被执行。
Call clone();
interface Factory {
Call newCall(Request request);
}
}
这个对象表示单个请求/响应对(流),不能执行两次。接着我们去Call接口的实现类RealCall
...省略N多代码
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//将任务加入dispatcher
client.dispatcher().executed(this);
//在当前线程中执行Call的getResponseWithInterceptorChain()方法
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
//取当前的返回数据Response
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);
//使用dispatcher分发给线程池
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
...省略N多代码
这里面new了一个匿名内部类:
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;
//返回response的回调,也不在主线程里面
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 {
//最终结束dispatcher();
client.dispatcher().finished(this);
}
}
}
同步请求与异步请求都用到了dispatcher()
,异步任务交给了线程池来做,下面我们大概的来看一下:
public final class Dispatcher {
//最大的请求数
private int maxRequests = 64;
//host请求数最大5
private int maxRequestsPerHost = 5;
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<>();
...省略代码
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;
}
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();
}
}
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.
}
}
...
}
集合队列Deque
继承至Queue
关于线程池的概念可以上网自行搜索,这里简单标注一下相关的参数概念:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
抛出:
IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小于 0,或者 maximumPoolSize 小于等于 0,或者 corePoolSize 大于 maximumPoolSize。
NullPointerException - 如果 workQueue、threadFactory 或 handler 为 null。
关于Queue
:
队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。在处理元素前用于保存元素的 collection。除了基本Collection操作之外,队列还提供额外的 insertion, extraction, and inspection 操作。每一种方法以两种形式存在:如果操作失败有的会抛出异常,有的会返回一个特殊值(null或者false,取决于操作)。插入操作的另外一种形式专门用于有容量限制的Queue。
一点点的Interceptor源码解释
Interceptor:观察,修改以及可能有问题的请求输出和响应请求的回来。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。拦截器接口中有intercept(Chain chain)
方法,同时返回Response。
上面我们在分析同步异步的时候都会看到一个方法getResponseWithInterceptorChain()
:
Response getResponseWithInterceptorChain() throws IOException {
// 构建一个完整的拦截器堆栈。
List<Interceptor> interceptors = new ArrayList<>();
//用户自己设置的拦截器
interceptors.addAll(client.interceptors());
//当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许的情况下复用当前连接
interceptors.add(retryAndFollowUpInterceptor);
//处理了cookie,一些报头字段,压缩请求
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//处理Cache,
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());
//将request传入拦截链中返回
return chain.proceed(originalRequest);
}
接下来看下RealInterceptorChain
的实现逻辑:
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
...省略无数代码
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);
//每个interceptors,都实现了Interceptor接口。所以都要重写Response intercept(Chain chain)方法。
//index + 1,让每个拦截链有自己的拦截器。
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");
}
...
return response;
}
}
一个拦截器的intercept方法所执行的逻辑大致分为三部分:
- 在发起请求前对request进行处理
- 调用下一个拦截器,获取response
- 对response进行处理,返回给上一个拦截器
这就是OkHttp拦截器机制的核心逻辑。所以一个网络请求实际上就是一个个拦截器执行其intercept方法的过程。而这其中除了用户自定义的拦截器外还有几个核心拦截器完成了网络访问的核心逻辑,按照先后顺序依次是:
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectIntercetot
- CallServerInterceptor
附上一张图:
上图与Interceptor的部分摘抄至这个博客。
下面我贴一下,在实际中添加缓存的例子:
private static OkHttpClient mOkHttpClient;
static {
initOkHttpClient();
}
/**
* 为okhttp添加缓存,如果服务器不支持缓存时,从而让okhttp支持缓存。
*/
private static class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 有网络时 设置缓存超时时间1个小时
int maxAge = 60 * 60;
// 无网络时,设置超时为1天
int maxStale = 60 * 60 * 24;
Request request = chain.request();
if (CommonUtil.isNetworkAvailable(Context)) {
//有网络时只从网络获取
request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
} else {
//无网络时只从缓存中读取
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response response = chain.proceed(request);
if (CommonUtil.isNetworkAvailable(Context)) {
response = response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
response = response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return response;
}
}
private static void initOkHttpClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
if (mOkHttpClient == null) {
synchronized (RetrofitHelper.class) {
if (mOkHttpClient == null) {
//设置Http缓存
Cache cache = new Cache(new File(Context.getCacheDir(), "HttpCache"), 1024 * 1024 * 10);
mOkHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(interceptor)
.addNetworkInterceptor(new CacheInterceptor())
.addNetworkInterceptor(new StethoInterceptor())
.retryOnConnectionFailure(true)
.connectTimeout(40, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
}
}
}
基本的用法都会,但是想深究一下实现的过程,就发现自己水平不够,看不下去了。