Okhttp源码解析(一)
Okhttp(官网、github)作为安卓主流的网络加载框架,其基本使用相信大家已经很熟悉,通过简单的依赖和设置参数即可完成网络的请求,且包含丰富的API方便调用,例如简单的图片加载实例。本文旨在学习其源码相关的知识,了解网络加载背后源码的执行流程,方便更好的使用该框架和解决问题。
1、网络加载的执行流程
文章图片
OkHttpClient client = new OkHttpClient
.Builder()
.build();
Request request = new Request
.Builder()
.url("http://www.baidu.com")
.build();
Call call = client.newCall(request);
//同步
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}//异步请求
call.enqueue(new Callback() {@Override
public void onFailure(Call call, IOException e) {
System.out.println("连接失败");
}@Override
public void onResponse(Call call, Response response) throws IOException {
//请求处理,输出结果
System.out.println(response.body());
}
});
结合上图和实例代码,okhttp的使用主要包括四部分:
- 创建OkHttpClient :设置一些基本的参数例如时间、缓存和拦截器等,同时初始化一些参数,例如Dispatcher、ConnectionPool和参数的默认设置等;
- 创建Request :该部分主要是请求参数的设置,例如URL、method、body和headers等;
- 获取新的Call :将创建的OkHttpClient 和Request 相结合构建一个请求对象;
- 执行网络请求操作:通过execute()或enqueue()真正执行网络请求。
public Builder() {
dispatcher = new Dispatcher();
...
connectionPool = new ConnectionPool();
...
}
在创建OkHttpClient 时,主要是通过build的形式构建其对象,相比使用时经常用到的API的设置(例如时间、缓存和拦截器等),我们这里关注的是两个参数:Dispatcher和ConnectionPool,其中Dispatcher是okhttp的构建核心,主导okhttp请求的主要流程,特别是异步请求时,管理其队列,下文会详细讲解。ConnectionPool是作为请求的连接池,用于请求的复用和策略相关的操作。
1.2 创建Request
与OkHttpClient 类似也是采用构建者的方式,Request 最主要的功能是设置URL、method和body,默认采用的是GET请求方式,可通过.post或.put方式切换至其他请求模式,其中body主要包括FormBody和MultipartBody。
1.3 获取新的Call
由于Call是一个接口,主要通过其实现类RealCall来完成OkHttpClient 和Request相结合,构建RealCall 。
//RealCall .java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
1.4 执行网络请求操作
完成以上三步的准备工作后,开始执行真正的网络请求操作,根据调用的方法(execute()或enqueue())来区分同步还是异步的请求操作,这里需要注意,同步和异步其中最大的区别为是否阻塞当前的工作线程,异步请求中会开启一个新的工作线程执行网络请求操作,通过callback的方式返回请求结果。
2、源码分析 在通过RealCall来完成OkHttpClient 和Request相结合后,开始执行网络请求的操作,根据execute()或enqueue()区分是同步请求或是异步,下面通过源码分析同步和异步请求的逻辑处理。
2.1 同步执行
文章图片
//RealCall .java
@Override public Response execute() throws IOException {
//1
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//2
captureCallStackTrace();
eventListener.callStart(this);
try {
//3
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 {
//4
client.dispatcher().finished(this);
}
}
结合UML和源码分析,其执行流程如下:
- 状态判断:通过executed 变量判断是否已经执行请求操作, 如已经执行则抛出异常;
- 状态捕获:捕获请求中一些堆栈信息便于流程分析;
- 请求操作:通过Dispatcher的executed()执行真正的网络请求操作,经过拦截器中一系列链式的请求操作后,将结果返回;
- 清理操作:请求完成后,通过Dispatcher的finshed()方法清理堆栈中的请求信息。
executed
//Dispatcher.java
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
在1.1中OkHttpClient的Builder的构建时,默认就创建好了Dispatcher对象,因此此时可以直接调用其方法,在executed中方法比较简单,直接将请求的RealCall 对象添加至同步正在执行的队列中。而后通过getResponseWithInterceptorChain()获取网络请求的Response;
【Okhttp源码解析(一)】finished
网络请求完成后,在最后的finally中会调用finished方法,其主要功能是清理堆栈中的对象。
//Dispatcher.java
private void finished(Deque calls, T call, boolean promoteCalls) {
...
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
...
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
2.2 异步执行
文章图片
//RealCall .java
@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));
}
异步请求流程与同步类似,但是在调用网络请求的方法为enqueue,并将responseCallback通过AsyncCall封装为参数,传递至Dispatcher的enqueue方法中。查看AsyncCall的源码可以发现,其继承自NamedRunnable,而NamedRunnable实现了Runnable方法,因此在执行异步操作时,会开启一个新的工作线程,避免线程阻塞,在AsyncCall中真正实现网络 的请求操作。
在执行Dispatcher的enqueue方法时,主要是将封装好的AsyncCall添加至队列中,在Dispatcher类针对异步定义了两个队列:readyAsyncCalls和runningAsyncCalls,用于存储等待线程和正在执行的线程。添加至那个队列通过如下的判断:
- 添加至正在运行的异步队列:正在运行的异步队列<64&&当前网络请求<5
- 添加至等待队列:当大于maxRequests 或maxRequestsPerHost添加至等待队列中
//RealCall .java
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当添加至正在执行的队列后,开始通过executorService线程池执行call线程,executorService的创建的参数比较有意思,如下所示,其核心的四个参数,corePoolSize=0,maximumPoolSize= Integer.MAX_VALUE,keepAliveTime =60,TimeUnit = TimeUnit.SECONDS。
其特点是没有核心线程,只有非核心线程,当异步请求加入线程池后,开始执行请求操作,并且在线程池完成请求操作后,其余未执行的线程会在60s后自动销毁。
这里需要注意,理论上可以添加Integer.MAX_VALUE,但是由于runningAsyncCalls的限制条件(maxRequests 和maxRequestsPerHost)所以不会无限添加。方法中添加synchronized 是保证executorService调用的单例性。
//RealCall .java
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
添加至线程池后,开始开启新的线程执行网络请求操作,上文可知AsyncCall最终实现了Runnable方法,并在run()调用抽象方法execute(),下面查看一下AsyncCall重写execute()的执行流程。
//RealCall .java
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
...@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) {
...
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
...
} finally {
client.dispatcher().finished(this);
}
}
}
执行流程包括:
- 执行网络请求链:和同步类似,通过getResponseWithInterceptorChain()获取请求结果;
- 是否调用取消操作:如调用取消回调至onFailure否则,将Response返回;
- 是否包含异常:如包含则调用onFailure和eventListener进行回调和时间记录;
- 清理操作:该步较为重要,用于清理和准备队列和执行队列中数据的添加。
//Dispatcher.java
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
在Dispatcher中,最终调用的是finished的重载方法中,如下所示。
private void finished(Deque calls, T call, boolean promoteCalls) {
...
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();
}
}
该方法在同步时,也会调用,但是不同的是传入的promoteCalls为true,则会执行promoteCalls方法 ,如下所示查看该源码发现,其主要作用就是readyAsyncCalls中的队列传递至runningAsyncCalls中。其他执行的流程与同步类似。
//Dispatcher.java
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return;
// Already running max capacity.
if (readyAsyncCalls.isEmpty()) return;
// No ready calls to promote.for (Iterator 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.
}
}
3、小结 本文主要通过OkHttpClient的简单的请求实例,来分析同步和异步请求的源码执行流程,其中有一个比较重要的是Dispatcher类,负责请求事件的分发和队列的维护。然而真正的数据请求是调用getResponseWithInterceptorChain(),通过拦截器链逐步的获取请求结果,下文会重点分析Interceptor相关的源码流程。
推荐阅读
- Android事件传递源码分析
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- Java内存泄漏分析系列之二(jstack生成的Thread|Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析)
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)
- ffmpeg源码分析01(结构体)
- Android系统启动之init.rc文件解析过程
- Java程序员阅读源码的小技巧,原来大牛都是这样读的,赶紧看看!
- 小程序有哪些低成本获客手段——案例解析
- Vue源码分析—响应式原理(二)
- SwiftUI|SwiftUI iOS 瀑布流组件之仿CollectionView不规则图文混合(教程含源码)