如需转载请评论或简信,并注明出处,未经允许不得转载
目录
前言
okhttp是square开源的轻量级网络框架
- 官网:https://square.github.io/okhttp/
- github地址:https://github.com/square/okhttp
依赖方式
"com.squareup.okhttp3:okhttp:4.2.1"
使用步骤
先来看看okhttp的基本使用方法。本文主要介绍原理,更多的使用方式可参考okhttp官网
GET请求
//1.创建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
String get(String url) throws IOException {
//2.创建Request对象
Request request = new Request.Builder()
.url(url)
.build();
//3.创建NewCall对象
//4.执行newCall.execute(),生成response对象
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
POST请求
//1.创建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2.设置mediaType
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
String post(String url, String json) throws IOException {
//3.创建请求体
RequestBody body = RequestBody.create(JSON, json);
//4.创建Request对象
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
//5.创建NewCall对象
//6.执行newCall.execute(),生成response对象
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
可以看出GET
请求和POST
请求的步骤其实都大同小异,主要分为如下几步
- 创建okHttpClient对象
OkHttpClient
对象有两种创建方式,一种是直接new OKHttpClient()
,还有一种可以通过建造者模式new OkHttpClient.Builder()
添加其他属性。实际上okhttp
在默认的构造方法中就已经赋值了很多属性的默认值
- 创建request对象
每一个http
请求都包含请求url
、请求方法(GET
/POST
)、请求头(Header
),同时也可能包含请求体(Body
)
- 创建response对象
response
就是对request
请求的一个回复,通过执行newCall
中的execute()
生成
源码分析
okhttp
支持同步请求和异步请求,execute()
是同步请求,enqueue()
是异步请求。下面我们通过源码来看一下response
是如何创建的(本人当前使用的okhttp版本的源码是基于kotlin)
同步请求
RealCall.kt
override fun execute(): Response {
synchronized(this) {
//1.先检查newcall是否被执行过,被执行过会抛出异常
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
//2.通过dispatcher调度器执行
client.dispatcher.executed(this)
//3.通过执行一系列拦截器链,最终返回response
//☆关于拦截器的内容下文会重点分析
return getResponseWithInterceptorChain()
} finally {
//4.执行完请求后,将请求从dispatcher调度器移除
client.dispatcher.finished(this)
}
}
Dispather.kt
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的同步请求队列
private val runningSyncCalls = ArrayDeque()
@Synchronized internal fun executed(call: RealCall) {
//将call请求放到双向队列中
runningSyncCalls.add(call)
}
异步请求
RealCall.kt
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
//1.和同步请求一样,先检查newcall是否被执行过,被执行过会抛出异常
check(!executed) { "Already Executed" }
executed = true
}
transmitter.callStart()
//2.执行dispatcher.enqueue()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Dispather.kt
/** Ready async calls in the order they'll be run. */
//缓存等待的异步请求队列
private val readyAsyncCalls = ArrayDeque()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在执行的异步请求队列
private val runningAsyncCalls = ArrayDeque()
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
//执行异步任务的线程池创建
//SynchronousQueue是一个内部只能包含一个元素的队列。
//插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。
//同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
//类似生产者和消费者模型
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
}
return executorServiceOrNull!!
}
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//先将请求添加到缓存等待队列
readyAsyncCalls.add(call)
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//执行请求
promoteAndExecute()
}
private fun promoteAndExecute(): Boolean {
assert(!Thread.holdsLock(this))
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//请求最大运行数不能超过64(maxRequests=64),最大主机连接数不能超过5(maxRequestsPerHost=5)
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
//从缓存等待队列中移除
i.remove()
asyncCall.callsPerHost().incrementAndGet()
executableCalls.add(asyncCall)
//添加到正在执行的异步请求队列
runningAsyncCalls.add(asyncCall)
}
//运行数>0说明正在运行
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//executorService是一个线程池ThreadPoolExecutor对象
asyncCall.executeOn(executorService)
}
return isRunning
}
Dispather功能
- 维护请求的状态
一个Call
只能被执行一次,否则抛出异常
- 维护请求队列
-
runningSyncCalls
:正在运行的同步请求队列 -
readyAsyncCalls
:正在执行的异步请求队列,请求最大运行数不能超过64,最大主机连接数不能超过5 -
runningAsyncCalls
:缓存等待的异步请求队列
- 维护线程池
创建了一个阀值是Integer.MAX_VALUE的线程池,它不保留任何最小线程,随时创建更多的线程数,而且如果线程空闲后,只能多活60秒。所以也就说如果收到20个并发请求,线程池会创建20个线程,当完成后的60秒后会自动关闭所有20个线程。他这样设计成不设上限的线程,以保证I/O任务中高阻塞低占用的过程,不会长时间卡在阻塞上
拦截器
通过response
的创建过程的分析,我们发现okhttp
在返回response
的过程中,会经过一系列的拦截器
拦截器是okhttp
提供的一种强大的机制,可以监视,重写和重试网络请求。下面是一个简单的拦截器,用于记录请求和相应的日志
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//1.请求前--打印请求信息
long t1 = System.nanoTime();
Log.i(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
//网络请求,并继续执行拦截器链
Response response = chain.proceed(request);
//3.网络响应后--打印响应信息
long t2 = System.nanoTime();
Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
拦截器可以链接。假设同时具有压缩拦截器和校验拦截器,则确定是先压缩数据然后对校验和进行校验,还是先对数据进行校验和然后进行压缩。okhttp
使用ArrayList
来管理拦截器列表,并通过责任链模式按顺序执行拦截器。用户可传入的 interceptor
分为两类:Application Intercetor和Network Interceptor
Application interceptors 应用拦截器
okClient.addInterceptor(new LoggingInterceptor())
Application Interceptor
是第一个 Interceptor
因此它会被第一个执行,因此这里的 request
还是最原始的。而对于 response
而言呢,因为整个过程是递归的调用过程,因此它会在 CallServerInterceptor
执行完毕之后才会将 response
进行返回,因此在 Application Interceptor
这里得到的 response
就是最终的响应,虽然中间有重定向,但是这里只关心最终的 response
- 不需要去关心中发生的重定向和重试操作。因为它处于第一个拦截器,会获取到最终的响应
- 只会被调用一次,即使这个响应是从缓存中获取的
- 只关注最原始的请求,不去关系请求的资源是否发生了改变,我只关注最后的 response 结果而已
- 因为是第一个被执行的拦截器,因此它有权决定了是否要调用其他拦截,也就是 Chain.proceed() 方法是否要被执行
- 因为是第一个被执行的拦截器,因此它有可以多次调用
Chain.proceed()
方法,其实也就是相当与重新请求的作用了
Network Interceptors 网络拦截器
okClient.addNetworkInterceptor(new LoggingInterceptor())
NetwrokInterceptor
处于第 6 个拦截器中,它会经过 RetryAndFollowIntercptor
进行重定向并且也会通过 BridgeInterceptor
进行 request
请求头和 响应 resposne
的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部是发生了一次重定向操作,所以NetworkInterceptor
可以比 Application Interceptor
得到更多的信息了
- 因为
NetworkInterceptor
是排在第 6 个拦截器中,因此可以操作经过RetryAndFollowup
进行失败重试或者重定向之后得到的resposne
- 为响应直接从
CacheInterceptor
返回了 - 观察数据在网络中的传输
- 可以获得装载请求的连接。
注意事项
- 推荐让
OkHttpClient
保持单例,用同一个OkHttpClient
实例来执行你的所有请求,因为每一个OkHttpClient
实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个OkHttpClient
实例,显然就是一种资源的浪费。 -
response.body().string()
只调用一次 - 每一个
Call
(RealCall
)只能执行一次,否则会报异常 - 子线程加载数据后,主线程刷新数据