Let’s first look at how OkHttp is used,
- A synchronous request
Execute using the execute method
fun requestSync(a) {
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val client = OkHttpClient.Builder()
.build()
val call = client.newCall(request)
val response = call.execute()
println("Synchronous request") println(response.body? .string()) }Copy the code
- An asynchronous request
Through the enqueue method
fun requestASync(a) {
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val client = OkHttpClient.Builder()
.build()
val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException){}override fun onResponse(call: Call, response: Response) {
println("Asynchronous request") println(response.body? .string()) } }) }Copy the code
As you can see, either synchronous or asynchronous, OkHttpClient creates a Call and executes the network request through the Call.
OkHttpClient
OkHttpClient is created using the constructor mode, set a series of parameters, and return the OkHttpClient with the build method of okHttpClient. Builder. Through the source code can be verified
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
/ /... The n configurable parameters are omitted
constructor() : this(Builder())
init{
// Initialize some other parameters
// ...
// Verify the validity of the parameter
verifyClientState()
}
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
/ /... N parameters are omitted
// Use Kotlin's apply to implement parameter assignment
fun dispatcher(dispatcher: Dispatcher) = apply {
this.dispatcher = dispatcher
}
// ...
// Construct returns OkHttpClient
fun build(a): OkHttpClient = OkHttpClient(this)}}Copy the code
As for the parameters that OkHttpClient can set, I won’t go into detail here. This article will only explain the general flow of OkHttpClient implementation.
RealCall
Once you have created OkHttpClient, you also need to create a RealCall object using the newCall method
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
// ...
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
// ...
}
Copy the code
Here you can see that a Request object is passed in to set the address of the Request, the header, etc. The Request class is also implemented using the Builder pattern.
Digging deeper into the RealCall class, we can see that synchronous requests use RealCall’s execute method and asynchronous requests use Enqueue. Let’s start with the execute method
class RealCall(
val client: OkHttpClient,
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
// Check whether network requests are already being executed to prevent multiple requests in multithreaded situations
private val executed = AtomicBoolean()
// ...
override fun execute(a): Response {
check(executed.compareAndSet(false.true)) { "Already Executed" }
// The timer starts to count requests timed out
timeout.enter()
// Notifies the user of the start of the request, and a user actively set listener is called back
callStart()
try {
// Add the request to the request queue
client.dispatcher.executed(this)
// Actually execute the request
return getResponseWithInterceptorChain()
} finally {
// Remove the call from the request queue
client.dispatcher.finished(this)}}}Copy the code
How do asynchronous requests work
override fun enqueue(responseCallback: Callback) {
// Check whether the request is already in progress
check(executed.compareAndSet(false.true)) { "Already Executed" }
callStart()
// Encapsulate an AsyncCall and throw it into the request queue
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code
You can see that both synchronous and asynchronous use something called a Dispatcher to put a request into a request queue, so let’s see what the Dispatcher does.
Dispatcher
OkHttp provides a default Dispatcher for managing each request, although users can also customize one. Let’s see how adding a synchronization request is implemented
class Dispatcher constructor() {
// Maximum number of requests at one time
@get:Synchronized var maxRequests = 64
/ /... Omit some code
// Maximum number of URL or IP requests at the same time
@get:Synchronized var maxRequestsPerHost = 5
/ /... Omit some code
// Asynchronous wait queue
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
// Execute queue asynchronously
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
// Synchronize the execution queue
private val runningSyncCalls = ArrayDeque<RealCall>()
@get:Synchronized
// Asynchronous request thread pool
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0.Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher".false))}return executorServiceOrNull!!
}
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
}
Copy the code
Simply, the Call is dropped into the runningSyncCalls queue. You can see that three queues and a thread pool are maintained in the Dispatcher for managing calls.
So asynchrony
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// Add to the asynchronous wait queue
readyAsyncCalls.add(call)
// Not a socket request
if(! call.call.forWebSocket) {// Find requests to the same host, which is used to limit the number of requests to the same host
val existingCall = findExistingCallWithHost(call.host)
if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
Copy the code
Now the promoteAndExecute method
private fun promoteAndExecute(a): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
// Fetch a Call from the wait queue
val asyncCall = i.next()
// Determine the current number of requests, if exceed the maximum, continue to wait, if not, continue to execute the following code
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() > =this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
// Throw the Call fetched into executableCalls
executableCalls.add(asyncCall)
// Drop the Call from the wait queue into runningAsyncCalls
runningAsyncCalls.add(asyncCall)
}
// The number of synchronous asynchronous requests being executed is >0
isRunning = runningCallsCount() > 0
}
// Iterate over the Call
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
// Call the executeOn method in AsyncCall
asyncCall.executeOn(executorService)
}
return isRunning
}
Copy the code
Dispacher also provides some methods of request termination, request cancellation, etc., which will not be explored here.
As can be seen from the above, asynchronous requests will eventually be wrapped into an AsyncCall and thrown into the thread pool for execution, so AsyncCall must be a Runnable, so let’s look at AsyncCall class
AsyncCall
AsyncCall is an inner class of RealCall. We will focus on two methods, executeOn and Run
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
fun executeOn(executorService: ExecutorService) {
// Check that the current thread does not hold the lock
client.dispatcher.assertThreadDoesntHoldLock()
try {
// Threads are executed through a thread pool
executorService.execute(this)}catch (e: RejectedExecutionException) {
// omit n lines of code
} finally {
if(! success) { client.dispatcher.finished(this) // This call is no longer running!}}}override fun run(a) {
// Sets the current thread name
threadName("OkHttp ${redactedUrl()}") {
// Start the timer
timeout.enter()
try {
// Execute the actual request
val response = getResponseWithInterceptorChain()
// Callback result
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
// The request failed
} catch (t: Throwable) {
cancel()
// The request failed
} finally {
// End of request
client.dispatcher.finished(this)}}}}Copy the code
Here you can see like a synchronous request, is called the RealCall getResponseWithInterceptorChain method request data, just return data way is different. So look at getResponseWithInterceptorChain method.
getResponseWithInterceptorChain
Obviously you can see here that OkHttp performs network requests using the chain of responsibility mode,
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(a): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
// There can be multiple user-defined interceptors
interceptors += client.interceptors
// Retry and redirect interceptors
interceptors += RetryAndFollowUpInterceptor(client)
// Bridge interceptors, mainly to set some common headers
interceptors += BridgeInterceptor(client.cookieJar)
// Cache interceptor
interceptors += CacheInterceptor(client.cache)
// Connect interceptor
interceptors += ConnectInterceptor
// If it is not a socket, add network interceptor
if(! forWebSocket) { interceptors += client.networkInterceptors }// Request interceptor, where network requests are actually executed
interceptors += CallServerInterceptor(forWebSocket)
// Create a chain of responsibility
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// Execute the chain of responsibility, one interceptor at a time, to get the result of the request
val response = chain.proceed(originalRequest)
// If the current request has been canceled, an exception is thrown
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")}return response
} catch (e: IOException) {
/ /...
} finally {
/ /...}}Copy the code
The entire request process is shown in figure 1
As you can see above, when we make a request, the user – defined interceptor (application interceptor) is invoked first, while the network interceptor is executed before the request is executed.
This analysis ends here, and we’ll see how each interceptor’s internal implementation works when we have time.