子曰:温故而知新,可以为师矣。 《论语》-- 孔子
在讲源码之前,我们先来略提一下 OKHttp
的简单实用,本篇文章主要还是对于 OKHttp
主流程的源码进行梳理。
一、基本使用
1.1 添加 Gradle
依赖
implementation("com.squareup.okhttp3:okhttp:3.10.1")
1.2 Get 请求
// get 请求
public void asyncGet(){
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("https:www.baidu.com")
.get()
.build();
final Call call = okHttpClient.newCall(request);
// 同步
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
System.out.println(response.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// 异步
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.d("TAG","onFailure:"+e.toString());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.d("TAG","onResponse"+response.toString());
}
});
}
1.3 post 请求
// post 请求
public void asyncPost(){
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
RequestBody formBody = new FormBody.Builder()
.add("ip","59.108.54.37")
.build();
Request request = new Request.Builder()
.url("http://ip.taobao.com/service/getIpInfo.php")
.post(formBody)
.build();
final Call call = okHttpClient.newCall(request);
// 同步
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
System.out.println(response.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// 异步
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.d("TAG","onFailure:"+e.toString());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.d("TAG","onResponse"+response.toString());
}
});
}
好了,现在正式开始本篇的主要内容,各位食客慢慢享用。
二、主线流程源码解读
1. 创建 OkHttp
对象。
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
首先创建一个 OkHttpClient
的对象,这个对象的创建采用了 构建者
模式。关于此模式,可以先看我写的这一篇文章Java 设计模式 之 构建者模式。
查看 OkHttpClient
这个类的源码,这个类里面有一个内部静态类
Builder
类。
// OkHttpClient 类中的静态内部类 Builder
public static final class Builder {
Dispatcher dispatcher;
//...
public Builder() {
dispatcher = new Dispatcher();
//...
}
}
所以调用 OkHttpClient.Builder()
方法得到 Builder
对象。我们可以看到这个 Builder
类中有很多属性,例如上面的 dispatcher
,我们可以这样设置:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.dispatcher(null) // 假设传null
.build();
那么我们在创建静态内部类 Builder
类的时候,就给此类中的 dispatcher
这个属性赋值了,然后调用这个类的 build()
方法,将 Builder
中的 dispatcher
属性值赋值给 OkHttpClient
类中的 dispatcher
,怎么传值的,我们来看一下 build()
方法:
// OkHttpClient 类中的静态内部类 Builder 类中的 build() 方法
public OkHttpClient build() {
return new OkHttpClient(this);
}
此 build()
方法返回一个 OkHttpClient
实例对象,它调用的是传入 Builder
类的带形参的构造方法:
// 含有 Builder 形参的 OkHttpClient 类的构造方法
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
//....
}
通过这个构造方法,就可以看出来将传入的Builder
中的 dispatcher
属性值赋值给 OkHttpClient
类中自身的 dispatcher
属性。
2. 创建 Request
对象。
Request request = new Request.Builder()
.url("https:www.baidu.com")
.get()
.build();
Request 对象的创建与 OkHttp
对象的创建过程类似,也是使用的构建者模式。
3. 创建 Call
对象。
Call call = okHttpClient.newCall(request);
这边调用了 newCall
方法,我们点击此方法,看一下源码:
// OkHttpClient 类中的 newCall() 方法
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
可以看到,此方法返回的是一个 Call
对象,也就是 Call
接口,源码中返回的是 RealCall
,那么不用看也知道这个 RealCall
是 Call
接口的实现类。
4. 异步调用。
call.enqueue(new Callback() {
@Override
public void onFailure( Call call, IOException e) {
Log.d("TAG","onFailure:"+e.toString());
}
@Override
public void onResponse( Call call, Response response) throws IOException {
Log.d("TAG","onResponse"+response.toString());
}
});
用上面的分析,我们可以知道实际上是调用了源码中 RealCall
的 enqueue()
方法,也就是调用 RealCall
类中重写了 Call
接口中的 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));
}
在这个方法中,有一个同步锁,executed 默认为 false
,一旦请求大于 1
次,那么就会抛出异常。在这个方法中,还有这么一行代码需要关注:
client.dispatcher().enqueue(new AsyncCall(responseCallback));
这边的 client
就是在 OkHttpClient
类中 调用 newCall()
方法,也就是 RealCall.newRealCall(OkHttpClient client ,...)
传入的第一个参数,也就是先拿到 OkHttpClient
类中的 dispatcher
(调度器),然后调用 Dispatcher
类中的 enqueue
方法,同时将传入的 callback
作为参数传入到了 AsyncCall
的构造器中作为形参,并且创建了 AsyncCall
。
对上面的分析汇总一下:
OKHttpClient.newCall()
创建的是RealCall
对象,call.enqueue()
实际调用RealCall.enqueue()
,此方法中有个client.dispatcher().enqueue()
方法,实际调用传入的OKHttpClient
类中dispatcher
属性所在类的enqueue()
方法。
好的,我们接着来看 Dispatcher
类中的 enqueue
方法:
// Dispatcher 类中的 enqueue 方法
synchronized void enqueue(AsyncCall call) {
// 同时运行的异步任务小于 64 && 同时访问(同一个)服务器小于 5 个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 把运行任务加入到运行队列中
runningAsyncCalls.add(call);
// 执行异步任务
executorService().execute(call);
} else {
// 加入到等待队列中
readyAsyncCalls.add(call);
}
}
runningAsyncCalls
和 readyAsyncCalls
分别是 运行队列
和 等待队列
。考虑到性能,使用的是 双端队列
。那么现在我们就看看 executorService().execute(call)
这一行代码,这行代码的意思是使用线程池执行 call
任务,那么线程池是如何工作的呢?我们可以看到是使用了 ThreadPoolExecutor
类来创建的线程池,对于此方法中的参数如下解释:
int corePoolSize
:核心线程数。
int maximumPoolSize
:线程池非核心线程池数,线程池规定大小。
long keepAliveTime
: 时间数值
TimeUnit unit
:时间单位
BlockingQueue<Runnable> workQueue
:超出的任务会添加到队列中,缓存起来。
参数一 和 参数二 的设置的值体现了缓存的思想,参数三 和参数四 是在 正在执行的任务数 大于 核心线程数的时候才有作用,打个比方,如果核心线程数是5,我们要执行的任务是 20 个,参数三设置为 60 ,参数四设置为秒,那么我们开启线程后,线程池中一开始会有 5 个线程任务,这五个线程任务执行后,参数三发挥作用,闲置 60s,过了闲置 60s 后,会回收掉任务,那么在 60s 内如果还有任务要执行,那么就会复用之前已开启的线程。我们举个栗子:
public class MyThreadPool {
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
for (int i = 0;i<20;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("当前线程,执行耗时任务,线程是"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
// 运行结果
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-3
在创建线程池的时候,Util.threadFactory("OkHttp Dispatcher", false)
这个参数通过 Util
帮助类,使用线程工厂给线程设置名字,同时设置不使用守护线程,也就是这个 false
参数。我们可以看一下 threadFactory()
这个方法源码。
public static ThreadFactory threadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable runnable) {
Thread result = new Thread(runnable, name);
result.setDaemon(daemon);
return result;
}
};
}
setDaemon(false)
就是设置不使用守护线程,守护线程的意思是 比如说我们在 main()
方法中开了一个线程,线程里写了一个死循环,如果线程 setDaemon(true)
,那么 执行一个时间片段后,JVM
虚拟机会认为当前进程需要结束,不会一直死循环下去,如果设置为 false
,那么会一直死循环下去。
线程池说完了,那么知道真正执行耗时操作的就是这个 AsyncCall
,那么这个 AsyncCall
是什么?
// RealCall 中的 内部类
final class AsyncCall extends NamedRunnable {
//...
}
可以看到这个类继承 NamedRunnable
类:
// NamedRunnable类
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();
}
我们可以看到 NamedRunnable
实现了 Runnalbe
接口 ,到时候执行耗时操作,那么就会走 run()
方法,而 run()
方法中的 execute()
是一个抽象方法,那么实际是走了继承 NamedRunnable
这个抽象类,并且重写了 execute()
方法的 AsyncCall
中的 execute()
方法,真真正正的执行耗时操作就是 AsyncCall
类中的 execute()
方法。 也就是说我们需要看 AsyncCall
中的 execute()
方法中的源码。
//AsyncCall 类中的 execute() 方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 通过责任链模式获取返回值 response
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);
}
}
上面这一串代码还是稍微好理解的,可能有一点困惑的就是这边作者做的异常处理,通过 signalledCallback
这个标识位,一旦请求成功,源码中给出了回调,我们自己在回调方法里面写错了,那么 signalledCallback
标识位就会为 true
,同时进入 catch
代码块,并且打印 Log
日志,说明此异常不是由 OKHttp
引起的,这里就是一个责任分明的思想体现。
写在文末
纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游
好了,关于 OKHttp
的源码的阅读就说到这,各位看官食用愉快。我们下一篇文章见。