使用
框架的具体使用详解这里就不展开了, 可以大概看一下这些文章或者之前总结的博客:
同步请求例程
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
//同步请求
public void synRequest() {
Request request = new Request.Builder().url("http://www.baidu/com").get().build();
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
OkHttpClient.Builder()
Builder是OkHttpClient中的一个内部类; 通过这个类我们可以配置请求的各种参数;- 注意第一行,是一个
Dispatcher; 分发器类,OKHttp的核心类; 管理同步或者异步请求的分发;
Call对象——连接Request和Response的桥梁; 同步请求和异步请求的分水岭;Call对象调用execute();为同步请求, 调用enqueue()为异步请求;
OkHTTP请求流程图
- 不管是同步请求还是异步请求,
最终都会走到
getResponseWithInterceptorChain(), 这个方法也是OKHttp的核心, 其内部是构建了一个拦截器的链, 然后通过依次执行拦截器链中的每一个拦截器,来获取服务器的数据返回;
异步请求例程
- 注意:
onFailure和onResponse都是在工作线程(即子线程中)去执行的;
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void asyncRequeset() {
Request request = new Request.Builder().url("http://www.baidu/com").get().build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("Fail");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}
同步请求和异步请求的区别
-
都需要创建
OkHttpClient实例,调用newCall()得到Call实例, 随后Call对象调用execute()为同步请求, 调用enqueue()为异步请求; -
同步请求阻塞主线程,异步请求不阻塞; 同步发送请求之后,【发送请求的进程】就会进入阻塞状态,直到收到响应为止; 异步请求则是开一个子线程去处理数据;
OkHttpClient.Builder()源码解析
OkHttpClient.Builder()使用了建造者模式
下面是Builder的一个构造函数:
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
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;
}
-
注意第三行,
connectionSpecs = DEFAULT_CONNECTION_SPECS;是重点,connectionSpecs是OkHTTP中,HTTP请求的分发器,异步请求由他决定是直接请求还是缓存等待;同步请求就没有太多的操作,是直接放在任务队列当中执行; -
接着看
connectionPool = new ConnectionPool();, 这是一个连接池,同样是OkHTTP中的重要概念;
理解连接池 —— 把服务端和客户端之间的一个连接,抽象为一个Connection, 而每一个Connection,OkHTTP都把它放在连接池ConnectionPool当中, 由ConnectionPool来对这些Connection进行统一的管理;
ConnectionPool作用: 作用一:当用户请求的Url是相同的时候,可以在ConnectionPool中选择复用【类>似线程池】; 作用二:ConnectionPool实现了链接分类等相关策略的配置的管理, 如哪一些网络连接 可以保持打开状态,哪一些是用来以后复用的这些策略配置都是由ConnectionPool进行管理的;
Request.Builder()源码分析
- Request同样使用了建造者模式
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
- build() 把使用Builder模式配置好的Builder对象, 赋值给Request的构造方法, 创建了一个Request对象; (思路讲解如《建造者模式》)
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
- Request的构造方法
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;
}
同步请求流程 源码分析
请求步骤例程回顾
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
//同步请求
public void synRequest() {
Request request = new Request.Builder().url("http://www.baidu/com").get().build();
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
1.创建一个OkHttpClient对象; 2.构建携带请求信息的Request对象; 3.将Request实例赋给newCall()得到Call对象; 4.使用Call对象调用execute或者enqueue方法;
则接着看 newCall()源码
- newCall() 实际上是 Call接口中 Factory接口 的接口方法,
而OkHttpClient实现了 Factory接口:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
newCall()实际操作逻辑都是在newRealCall()中:newRealCall()中创建了一个RealCall实例, 再赋值了一个Listener;RealCall的构造方法,则是初始化各个属性:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
注意这里,传进来的
Request实例, 最终是作为一个RealCall实例的成员变量, 而这个RealCall实例, 就是我们应用时要拿去进行同步(execute())或异步请求(enqueue())的句柄; 【也就是 请求信息配置类(Request) 跟 请求启动的句柄(RealCall)绑定在一起了, 后续要进行相关的控制(如同步请求的同步判断控制)是很方便的;】
- 然后
newRealCall()返回了一个RealCall实例, 这个RealCall实例又作为newCall的返回值, 返回到我们使用框架的层次,向上转型为Call实例去应用:
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
- 所以后续我们使用
Call对象【实际上是一个向上转型的RealCall实例】调用execute()或者enqueue()的时候, 这里的execute()或者enqueue()的逻辑, 即RealCall实现好的execute()或者enqueue()中的逻辑; - 因为
execute()和enqueue()在Call中只是作为接口方法被定义, 而RealCall类实现了Call接口以及Call接口中的方法,包括execute()和enqueue():
接着是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);
}
}
-
首先是synchronized 代码块, 首先判断
executed是否为true, 也就是说同一个HTTP请求,只能执行一次; 如果请求执行过,就会抛出"Already Executed"异常; 没有执行过,则执行,同时executed置位; -
captureCallStackTrace();捕捉HTTP请求的异常堆栈信息; -
eventListener.callStart(this);开启一个监听事件; 【每当call实例调用execute()或者enqueue()的时候,就会调用这个方法,后面细讲】 -
接下来的代码划重点, try中:
client.dispatcher().executed(this);dispatcher()方法放回一个Dispatcher对象,它是请求的分发器;
executed()方法——重点同步请求,call 调用
executed(),就是把RealCall(Call)对象调用实例本身, 加到(add(this))同步队列runningAsyncCalls中,就完成了这个操作;
Dispatcher简要介绍
维护了Call请求的一些状态, 同时也维护了一个线程池 用于进行网络请求; Call请求 通过 Dispatcher 推到 执行队列中 进行操作
-
执行完
executed()之后,紧接着是,Response result = getResponseWithInterceptorChain();getResponseWithInterceptorChain()用来获取Response; 其内部是构建了一个拦截器的链, 然后通过依次执行拦截器链中的每一个拦截器,来获取服务器的数据返回; -
最后是
execute()源码末尾的finally中的,client.dispatcher().finished(this);, 会主动回收当前同步请求;把
当前同步请求对应的Call 实例传进来, 然后202行,则是把该Call 实例从同步请求队列 runningAsyncCalls中 移除; 如果不能移除,就抛出异常 ——throw new AssertionError("Call wasn't in-flight!");; 当然这里注意是同步请求操作【execute()源码】,才是移除; 203行这里,由于同步请求这里promoteCalls位传false,这里不会走promoteCalls()方法; 204行,调用runningCallsCount()计算目前还在运行的请求,其实现如下:没什么特别的,就是直接返回正在执行的
异步请求和同步请求的总和;
到208行,有一个判断, 当正在执行的请求数为0的时候(表示当前整个Dispatcher当中,没有可运行的请求了), 这个时候再满足idleCallback != null,则调用idleCallback.run();
至此,同步请求的流程源码就告一段落了。 小结注意: 在同步请求当中,
Dispatcher类的作用,非常简单: 添加同步请求到同步请求列表, 从同步请求列表中移除同步请求;
异步请求流程 源码分析
- 异步请求例程
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void asyncRequeset() {
Request request = new Request.Builder().url("http://www.baidu/com").get().build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("Fail");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}
RealCall.enqueue()源码分析
-
94行,首先,锁住当前RealCall对象; 95、96行,同上,用于控制 --- 同一个HTTP请求,只能执行一次; 首先,判断executed是否为true, 如果为true(请求执行过),就会抛出"Already Executed"异常; 没有执行过,则执行,同时executed置位;
-
98行 ---
captureCallStackTrace();捕捉HTTP请求的异常堆栈信息; -
接下来重点看一下100行这里, 传进来的用户自定义的
Callback匿名内部类实例, 在这里被封装成一个AsyncCall实例;
!!!!!
AsyncCall源码:!!!!!首先
AsyncCall继承了NameRunnable, 然后AsyncCall中的execute()是实现了NameRunnable中定义的方法,跟RealCall中的execute()毫无关系,同名不同人;
再看
NameRunnable,其实就是一个实现了Runnable的Runnable子类; 所以其实AsyncCall类就是Runnable子类;
- 所以
enqueue()源码中,100行这里,client.dispatcher().enqueue(new AsyncCall(responseCallback));就是通过传进来的Callback实例,封装好了一个Runnable实例【AsyncCall实例】, 接着通过client.dispatcher()获取Dispatcher实例并调用enqueue()将这个Runnable实例送进执行流程;
- OkHttp.Builder构造方法第一行,已经实例化好
Dispatcher对象:
接着看Dispatcher.enqueue() 源码分析
-
首先,方法是同步方法(
synchronized), 然后参数是AsyncCall实例,刚刚提到过,是一个Runnable实例; -
接着130行, 首先判断
runningAsyncCalls.size() < maxRequests, 【即异步执行队列的元素数量是否小于最大请求数(64);】 以及runningCallsForHost(call) < maxRequestsPerHost, 【即正在运行的主机请求是否小于设定的最大值(5); 】如果 满足以上条件, 则把传递进来的
AsyncCall/Runnable实例 添加到runningAsyncCalls中【131行】, 然后会使用一个线程池去执行它;【132行executorService()会返回一个线程池对象】如果不满足, 则把这个传递进来的
AsyncCall/Runnable实例 添加到 等待序列readyAsyncCalls中;
- 注意这里
runningAsyncCalls存储正在运行的异步任务,主要作用是判断并发请求的数量,readyAsyncCalls表示缓存等待中的请求的队列:
executorService().execute(call);源码分析【132行切入】:executorService()会返回一个线程池对象, 接着调用execute(call);会执行call实例的run(); 这里的call实例即是AsyncCall实例;接下来我们要看一下
run()的内容,AsyncCall源码如上,可以发现这里没有run()的内容, 而上面也说过了,AsyncCall继承自NameRunnable, 接着可以到NameRunnable中去找:到这就找到
call实例的run()了;run()中主要是调用了execute()方法,即【38行】处定义的execute(), 接着跟踪其实现,到RealCall.AsyncCall # execute()这里:【147行】
getResponseWithInterceptorChain(),构建了一个拦截器链【后续细讲】; 【148行】判断拦截器链中的 这个重试重定向拦截器 -- retryAndFollowUpInterceptor, 判断其是否取消了, 【149 - 150行】如果取消了,就会调用responseCallback.onFailure(), 【152 - 153行】如果没有取消,则走到responseCallback.onResponse(RealCall.this, response);; 【155行】处理异常情况; 【164行】client.dispatcher().finished(this);调用到(其实类同前面的同步请求)【201行】因为这边的几个队列都是不安全的队列, 所以这里201行需要加
synchronized关键词, 把remote()和promoteCalls()操作给锁住,保证线程安全;【202行】将当前
call实例移出对应异步请求任务队列; 【203行】调用promoteCalls()调整整个异步请求任务队列; 【204行】重新计算正在执行的线程数量,刷新赋值;用于后续判断操作;【208行】
####- Callback 引用的 来龙去脉 这里的
responseCallback实例 就是我们一开始调用框架时传进来的自定义的 CallBack实例;
- 从源码角度 理解【onFailure()】、【onResponse()】执行与子线程
到这里可见,与一开始相呼应的一个点: 无论这里是
responseCallback.onFailure()或者是responseCallback.onResponse(RealCall.this, response);, 都是在子线程中运行的!!!!!!!!!!!!! 因为它们都是编写在execute()方法中的, 而execute()方法在一个Runnable的run()中, 然后这个Runnable实例正式用线程池开启的子线程来执行的。
所以更新UI等任务,注意切换到主线程操作!
- 这里
AsyncCall中的execute()是实现了NameRunnable中定义的方法,跟RealCall中的execute()毫无关系,同名不同人;RealCall中的execute()是对Call中定义的execute()实现;
从OkHttpClient的创建,到最后的
Dispatcher# finished(),一次完整的OkHttp异步请求就完成了;
OkHttp的任务调用 —— 主要基于Dispatcher类实现
Dispatcher的概述与源码分析
-
OkHttp中,发送请求的 同步、异步 的状态 都会在Dispatcher中 被管理, 也由此区分 请求的 同步、异步;
-
Dispatcher用于维护( 同步、异步)
请求的 状态, 并维护一个线程池,用于(更加高效地)执行请求(尤其异步请求); 维护任务队列; -
图解Dispatcher作用、流程:
-
每当有网络请求的时候,通过
Call实例封装, 接着通过Dispatcher将Call请求实例推到readyAsyncCalls就绪请求队列中;
Dispatcher源码分析
- 首先是这三个请求队列:(前面讲过好几次了)
runningAsyncCalls--- 正在执行的异步请求; 注意看注释:列表还包含了已经取消、但没有执行完成(Finished)的异步请求;readyAsyncCalls--- 就绪状态的异步请求队列; 当某个异步请求 Call实例不满足条件时,则把这个异步请求 Call实例