Android API23(6.0)版本之后,Google正式移除Apache-HttpClient。OkHttp做为一个现代,快速,高效的HttpClient,其功能之强大也是显而易见的html
- 支持HTTP/2协议,经过HTTP/2 可让客户端中到服务器的全部请求共用同一个Socket链接
- 非HTTP/2 请求时, OkHttp内部会维护一个线程池,经过线程池能够对HTTP/1.x的链接进行复用,减小延迟
- 支持post,get请求,基于http的文件上传和下载
- 默认状况下,OkHttp会自动处理常见的网络问题,像二次链接、SSL的握手问题
固然OkHttp的功能远不止这些,这里只是说明平时常常用到的。既然OkHttp已经做为官方库使用,相比咱们在作项目的时候也会用,但对于其底层的实现原理仍是只知其一;不知其二,那咱们就从这篇文章开始解释其底层实现原理。开车前先来一波介绍:java
Ecplise引用:下载最新的Jar包android
Android studio引用:implementation 'com.squareup.okhttp3:okhttp:3.11.0' //最新版本号请关注okhttp官网
git
Maven引用:github
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version> //最新版本号请关注okhttp官网
</dependency>
复制代码
各位老司机们,立刻开车,嘀嘀嘀!web
流程以下apache
// 启动客户端类,主要有两种方法进行建立,new对象和Builder内部类实现实例化
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
// get请求
// 经过Builder模式建立一个Request对象(即请求报文)
// 这里能够设置请求基本参数:url地址,get请求,POST请求,请求头,cookie参数等
Request request = new Request.Builder()
.url("http://www.baidu.com")
.header("User-Agent", "xxx.java")
.addHeader("token", "xxx")
.get()
.build();
// POST请求
// 表单形式上传
RequestBody body = new FormBody.Builder().add("xxx","xxx").build();
// JSON参数形式,File对象上传
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
RequestBody body = RequestBody.create(MediaType.parse("File/*"), file);
Request request = new Request.Builder()
.post(body)
.url(url)
.header("User-Agent", "xxx.java")
.addHeader("token", "xxx")
.build();
// 建立Call对象(Http请求) ,即链接Request和Response的桥梁
// newCall方法将request封装成Call对象
Call call = client.newCall(request);
try{
// Response即响应报文信息,包含返回状态码,响应头,响应体等
Response response = call.execute();
// 这里深刻一点,Call实际上是一个接口,调用Call的execute()发送同步请求实际上是调用了Realcall实现类的方法,Realcall从源码能够看出示一个Runable
System.out.println(response.body().string());
}catch(IOException e){
e.printStackTrace();
}
复制代码
看完代码你可能以为OkHttp基本流程很繁琐,可是去掉一些扩展参数,你会发现OkHttp的使用其实很简单,无非就是json
- 建立一个OkHttpClient并实例化,可设置相关参数链接时长connectTimeout等
- 建立一个Request对象并实例化,可设置网络地址url,请求方式get,post,携带参数等;
- 建立一个Call对象,经过okhttpClient的newCall()方法将Request封装成Call对象
- 建立一个Response响应,用于接收服务器返回的相关信息; 即OkHttpClient客户端经过newCall()方法接受你的Request请求并生成Response响应
看到这里你可能会问,为何不继续讲些关于文件上传,文件下载,Interceptors拦截器这些内容?其实同步和异步请求的实现能够说是源码中很是重要的一环,涉及到线程池
,Dispatch调度
,反向代理
等,掌握核心科技,剩下的都是开胃小菜。缓存
1)基本使用方法安全
同步请求方法
OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
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和Request对象 2.将Request封装成Call对象 3.调用Call的excute()发起同步请求
*特别注意*:
当前线程发送同步请求后,就会进入阻塞状态
,直到数据有响应才会中止(和异步最大的不一样点
)
异步请求方法
OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() { //调用异步请求,CallBack用于请求结束之后来进行接口回调
@Override
public void onFailure(Call call, IOException e) { System.out.println("Failure");}
@Override
public void onResponse(Call call, Response response) throw s IOException {
System.out.println(response.body().string());
}
});
复制代码
经过上面代码能够看出异步请求的基本流程: 1.建立OkHttpClient和Request对象 2.将Request封装成Call对象 3.调用Call的enqueue发起异步请求
*特别注意*:
onFailure和onResponse都是执行在子线程
中
不难看出,其实异步和同步请求的不一样点
在于
发起请求方法调用
是否阻塞线程
到此,咱们已经熟悉了OkHttp的同步,异步请求方法的基本使用;无论同步仍是异步的调用都须要先初始化OkHttpClient,建立Request ,调用OkHttpClient.newcall()封装Call对象,但其内部又是如何实现的呢?
同步请求执行流程
第一步:初始化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; // cookie
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的初始化已经帮咱们配置了基本参数,咱们也能够根据自身业务需求进行相应的参数设置(失败重连,添加拦截器,cookie等等),通常遇到建立对象须要大量参数时,推荐使用Builider模式链式调用完成参数初始化,具体使用能够去Android源码中的AlertDialog、Notification中详细了解; 这里咱们重点注意两个核心,Dispatcher和ConnectionPool,这两点会在后面作详细讲解
Dispatcher
:OkHttp请求的调度分发器,由它决定异步请求在线程池中是直接处理仍是缓存等待,固然对于同步请求,只是将相应的同步请求放到请求队列当中执行
ConnectionPool
: 统一管理客户端和服务器之间链接的每个Connection,做用在于
- 当你的Connection请求的URL相同时,能够选择是否复用;
- 控制Connection保持打开状态仍是复用
第二步:建立 (Builder builder)
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body; //请求体
Object tag; //标签
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder(); //Headers内部类
}
复制代码
这个构造方法很简单,在Request.Builder模式下默认指定请求方式为GET请求,建立了Headers内部类来保存头部信息,咱们再来看build方法
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
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;
}
复制代码
Request的构造方法就是为其初始化指定需求的请求方式,请求URL,请求头部信息,这样就完成同步请求的前两步
第三步:调用OkHttpClient.newcall()封装Call对象
/**
* Prepares the {@code request} to be executed at some point in the future.
* 准备在未来某个时候执行{@code请求}
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
复制代码
上面咱们也提到过,Call是一个接口,因此它的实际操做是在RealCall类中实现的
final class RealCall implements Call {
final OkHttpClient client;
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; //重定向拦截器
/**
* There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward.
* This will be set after we create the call instance then create the event listener instance.
*/
private EventListener eventListener;
/** The application's original request unadulterated by redirects or auth headers. */ final Request originalRequest; final boolean forWebSocket; // Guarded by this. private boolean executed; //实际构造方法 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; } } 复制代码
从这里就能够看到,RealCall实际上是持有以前初始化好的OkHttpClient和Request对象,同时赋值了RetryAndFollowUpInterceptor重定向拦截器,关于拦截器的内容,咱们会后面具体讲解OKhttp内部的5大拦截器;
第四步,调用call.exucte方法实现同步请求
@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); // 调度器将call请求 加入到了同步执行队列中
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,确保每一个call只能被执行一次不能重复执行,而后开启了eventListener监听事件,接收相应的事件回调,经过dispatcher将Call请求添加到同步队列中
public Dispatcher dispatcher() {
return dispatcher;
}
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */ // 同步请求队列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); 复制代码
每当调用executed同步方法时,dispather就会帮咱们把同步请求添加到同步请求队列中去,由此能够看出Dispather调度器的做用就是维持Call请求发送状态
和维护线程池
并把Call请求添加到相应的执行队列
当中,由它决定当前Call请求是缓存等待仍是直接执行,流程以下
getResponseWithInterceptorChain()是一个拦截器链,依次调用拦截器对返回的response进行相应的操做,咱们在讲解到责任链模式时会详细介绍,如图
另外要特别注意下client.dispatcher().finished(this);
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false); // 注意参数传递的值
}
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();
}
}
/***********************************************************************************************************************/
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
复制代码
当同步请求完成后会调用finished()方法将队列中的请求清除掉,runningCallsCount()计算返回正在执行同步请求和正在执行异步请求的数量总和,最后判断若是runningCallsCount 为0的时候,表示整个Dispatcher分发器中没有可运行的请求,同时在知足idleCallback不为空的状况下,就调用Run方法开启闲置接口;这里能够看出,在同步请求的方法中,dispatcher的做用只是调用 executed将Call请求添加到同步队列中,执行完毕后调用 finished清除队列中的请求,可见dispatcher更多的是为异步服务
异步请求执行流程
关于OkHttpClient和Request初始化流程上文已经讲解,不清楚的能够返回去看看,因此直奔主题
第四步,调用call.enqueue方法实现异步请求
//RealCall实现类
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true; // executed用于表示Call请求是否执行过
}
captureCallStackTrace();// 捕捉异常堆栈信息
eventListener.callStart(this);// 开启监听事件
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
复制代码
有没有发现和同步的excute方法很相似,都是先使用synchronized 防止请求重复执行,而后开启监听事件,最后在执行相应的方法,但奇怪的是同步在执行完excute方法后是直接经过getResponseWithInterceptorChain()返回数据,异步又是如何返回数据的呢?AsyncCall又是干什么的?
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = 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) {
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);
}
}
}
复制代码
这里的 AsyncCall 是 RealCall 的一个内部类,它继承于NamedRunnable抽象类,NamedRunnable抽象类又实现了 Runnable,因此能够被提交到ExecutorService上执行,在execute方法里,咱们看到了熟悉的流程,上文也说到getResponseWithInterceptorChain是一个拦截器链,会依次执行相应的拦截器后返回数据,因此当返回数据后,经过retryAndFollowUpInterceptor重定向拦截器判断请求是否正常执行,而且经过Callback接口返回相应数据信息,最后调用finished方法清除队列 这里有个疑问,Dispatcher是经过什么把异步就绪队列的请求调度分发
到异步执行队列中的?
//Disaptcher
/** 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<>();
private void promoteCalls() {
// maxRequests最大请求数量64
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.
}
}
复制代码
源码是否是很清楚明了,原来异步队列就是在这里进行调度的,在for循环中,Disaptcher首先对异步就绪队列进行遍历,若是知足runningCallsForHost(当前调用请求主机数)小于maxRequestsPerHost( 最大请求主机数5个)而且异步并发数量没有超过最大请求数量64的前提下,就把异步就绪队列中最后一个元素移除加入到异步执行队列中
咱们接着看enqueue方法具体作了哪些操做//Disaptcher
synchronized void enqueue(AsyncCall call) {
// 异步并发请求数量不能超过最大请求数量64
// 当前网络请求的host是否小于5个请求的host
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 加入执行队列 并交给线程池执行
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
// 加入就绪队列等待
readyAsyncCalls.add(call);
}
}
***************************************************************************************************************
public synchronized ExecutorService executorService() {
// 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
复制代码
Disaptcher的enqueue方法只是作了一个异步请求的逻辑判断,即判断当前异步并发执行队列的数量是否超过最大承载运行数量64和相同host主机最多容许5条线程同时执行请求,知足以上条件,则将传进来的AsyncCall添加到异步执行队列,同时启动线程池执行,反之则添加到异步就绪队列中等待,executorService调用的就是AsyncCall的execute方法
同步和异步请求的源码就讲到这里,对过程还有不理解的能够在下方评论中提出问题 文章连接: