okhttp 请求流程(一)
- okhttp 简介
- okhttp 用法
- okhttp 分发器
- okhttp 拦截器
一、OKHttp 简介
okhttp git 地址:https://github.com/square/okhttp
1.1 简介
替代 HttpUrlConnection 和 Apache HttpClient(android API23 6.0里已移除HttpClient,现在已经打不出来)
OkHttp是一个默认高效的HTTP客户端:
1.2 okhttp 优势
允许连接到同一个主机地址的所有请求,提高请求效率
共享Socket,减少对服务器的请求次数
通过连接池,减少了请求延迟
缓存响应数据来减少重复的网络请求
减少了对数据流量的消耗
自动处理GZip压缩
支持同步异步调用
1.3 请求流程
二、okhttp 使用
2.1 okhttp 的集成
implementation "com.squareup.okhttp3:okhttp:4.7.2"
OkHttp包含一个用于测试HTTP、HTTPS和HTTP/2客户端的库。
testImplementation "com.squareup.okhttp3:mockwebserver:4.7.2"
2.2 okhttp 常用的请求对象
- OkHttpClient :用来发送http 请求和 获取网络请求响应体
- Request
- Request.Builder
- Call
- ResponseBody
2.2.1 OKHttpClicent
1、简介:
OkHttpClient :用来发送http 请求和 获取网络请求响应体,每个OkHttpClient 都拥有自己独立的的连接池和线程池,为了节省资源我们可以共用 线程池 和 连接池,okhttp 可设计为单例
2、okhttpClient 创建:
public final OkHttpClient client = new OkHttpClient(Builder builder)
new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor))
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
okHttpClient.Builder 常用方法:
- connectTimeout: 设置连接超时时间,如果设置为0,意思是没有超时时间,否则必须设置1 ~ Integer.MAX_VALUE 之间的整数,吗,默认连接超时时间为 10s
- readTimeout : 为 TCP Socket 和单个读IO 为 设置 读取超时时间,如果设置为0代表没有超时时间,否则必须设置1 ~~Integer.MAX_VALUE 之间的整数,默认是10s
- writeTimeout : 为 单个的写IO操作 设置 写入超时时间,如果设置为0代表没有超时时间,否则必须设置1 ~~Integer.MAX_VALUE 之间的整数,默认是10s
- proxy: 设置 http代理
- addInterceptor : 添加拦截器
- addNetworkInterceptor :添加网络拦截器
3、Request
请求对象:包含了HttpUrl, method, headers,RequestBody ,tag
method方法:
- GET
- POST
- DELETE
- PUT
三、Dispatcher 分发器:
网络请求执行有两种方式
- 同步请求
- 异步请求
请求代码:
OkHttpClicent client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor))
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
Request request = new Request.Builder()
.url("")
.build();
//创建 call 对象
Call call = httpClient.newCall(request);
//执行同步请求
Response response = call.execute();
1、发起一个OKhttp 请求
OkHttpClicent client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor))
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
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;
}
在发起一个OKhttp 请求至少用到了三个对象, OKHttpClient ,Request ,Call 对象
httpClient 对象 通过newCall 方法 接收传入的 Request.Builder 提供的request 对象生成一个Call对象。其中 OKHttpClient 以及 Request 给我们提供了Builder 对象供我们去配置使用。
建造者模式:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。实例化OKHttpClient和Request的时候,因为有太多的属性需要设置,而且开发者的需求组合千变万化,使用建造者模式可以让用户不需要关心这个类的内部细节,配置好后,建造者会帮助我们按部就班的初始化表示对象
同时OkHttp在设计时采用的门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端OkHttpClient统一暴露出来。
Call 是一个接口,封装了将发起的 okhttp 请求的 request 对象,可以对对请求进行取消 以及执行,newCall 方法生成了一个Call接口的实现对象 RealCall
- execute 方法:同步方法
- enqueue 方法:异步方法
在 RealCall方法中 实现了Call 接口的 execute (这节先不管enqueue 方法),同时发起了一个网络请求,这里涉及了请求流程中的dispatcher
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//分发器分发请求 添加到 readyAsyncCalls
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);
}
}
3.1 同步请求
网络请求的发起控制是通过 dispatcher 来处理的,dispatcher
/**
* Used by {@code Call#execute} to signal it is in-flight.
*/
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
OKhttp 中 用到了两个双端队列,其中一个双端队列( runningSyncCalls )用于保存正在执行的Task,另外一个双端队列 保存准备执行的( readyAsyncCalls )任务 (readyAsyncCalls 保存了准备执行的任务),
> 双端队列:deque,全名double-ended queue 是一种具有队列和栈性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除在表的两端进行。
在 通过 executed 方法中 同步请求被 加入 名为 runningSyncCalls 的双端队列 经过一系列的拦截器,返回 Response 来获取网络数据,通过 执行tryCatch 的finally 方法 Dispatcher.finish 方法 处理返回成功是的请求移除
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();
}
}
请求处理流程:
OkHttp 中网络请求通过Dispatcher 来分发请求,dispatcher 通过 runningSynCall 双端队列来对请求进行保存,执行dispatcher 的 executed 方法把一个请求添加到 runningSyncCalls 双端队列中,随后经过一系列的拦截操作返回 Response 对象, 当网络请求执行完成之后 通过 dispather 的finish 方法 把该请求移除双端队列。 为了保证同步 内部方法都用了synchronized 进行修饰。
3.2 异步请求
dispatcher 通过 enqueue 方法来添加一个异步请求,dispatcher 定义了 runningAsyncCalls ,readyAsyncCalls 来控制请求的添加与移除。异步请求的执行是通过 创建了一个线程池 创建Runnable 任务来执行 AsyncCall 任务 , 异步请求限制了最大request 请求个数不能超过 64 ,同一域名的请求数不能大于 5 个,这些参数可以通过 okhttpClient 提供的Builder 进行修改。
//最大请求书
private int maxRequests = 64;
//同一域名最大请求数
private int maxRequestsPerHost = 5;
private @Nullable
Runnable idleCallback;
/**
* Executes calls. Created lazily.
*/
private @Nullable
// 线程池对象
ExecutorService executorService;
/**
* 存放准备执行的异步任务
* 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<>();
dispatcher 定义以上参数来对异步请求进行控制:
//runningAsyncCalls 正在运行的异步请求数量
//runningCallsForHost(call) 统一域名的请求数量
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当数据请求数 >= 64 的时候 请求被放入的readyAsynCalls,否则放入了runningAsynCalls
@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)); //创建一个 AsynCall 放到 双端队列中去
}
异步请求通过 执行queue 传入我们创建的CallBack 对象 add 到 runningAsyncCalls 如果请求数目大于 64 或者 同一个host 的请求数目大于 5 那么这个AsycCall 会被放入 runningAsynCalls ,通过 executorService() 方法创建的线程池 去执行请求 当请求经过 分发拦截器 一系列处理完成之后 调用 dispatcher 的 promoteCalls 方法 进行 readyAsyncCalls 轮询 放入 runningAsyncCalls
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.
}
}
上面提到异步请求使用线程池执行的:
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;
}
ThreadPoolExecutor 5 个参数:
corePoolSize: 核心线程数目
maximumPoolSize:最大线程数
keepAliveTime: 当线程数大于 corePoolSize 空闲线程的存活时间
TimeUnit : 时间单位
BlockintQueue : 任务队列(阻塞队列)
线程工作机制
1、创建任务个数小于 corePoolSize 线程创建 任务个数的线程去执行任务
2、创建任务个数 等于或大于 corePoolSize ,任务会添加到 BlockQueue 队列中
3、如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务
4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
线程的饱和策略:
- AbortPolicy 直接抛出异常,默认策略
- CallerRunsPolicy:用调用者所在的线程来执行任务
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
- DiscardPolicy:直接丢弃任务
在上面看到 创建线程池的 的corePoolSize 的个数为0,而且使用到了 SynchronousQueue 无容量的队列,线程的corePoolSize 为0 无论如何任务添加到BlockQueue 都会失败,则会创建新的线程去执行提交的任务,完全没有任何等待,最大限度的保证的并发量。