As the network request framework okhttp is mainly promoted by Google, many times we use it directly without knowing how to do it internally. If you know the internal process, it will be much easier to expand and deal with some bugs. Next, let’s briefly analyze the request process.
First we create an okhttpclient
//Use default configuration val client = OkHttpClient.Builder().build()
Next, we will explain the get request and post request respectively.
Initialize our url request information
// This is a configuration operation, you can also write the URL directly val httpurl = HttpUrl.Builder() .scheme("https") .host("www.baidu.com") // add path .addPathSegments("mobileapi2") .addPathSegments("index.php") //Add parameters .addQueryParameter("act", "goods") .addQueryParameter("op", "goods_list") .build() //The final url is like this https//"www.baidu.com/mobileapi2/index.php?act=goods & amp;op=goods_list
If it is a get request, we can then use the addQueryParameter method to add the parameters we need. If it is a post request, we need to create a request body.
val builder = FormBody.Builder() builder.addEncoded("page", "10") builder.addEncoded("curpage", "1") val formBody = builder.build()
Then build one of our requests by creating request
val request = Request.Builder() .url(httpurl) //Configured url information // .get() get request // .post(formBody) post request post unique request body .build()
finally passed
//Asynchronous client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { } override fun onResponse(call: Call, response: Response) { } }) // Synchronize client.newCall(request).execute()
Our request has been completed, but how is it handled internally?
Our first choice is to look at what the newCall method does
// Created a new RealCall. RealCall inherits Call. The first parameter is our client. override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
Let’s look at the enqueue method inside RealCall
//When our client is built, the dispatcher is initialized by default, and the dispatcher maintains a thread pool and a request queue internally. client.dispatcher.enqueue(AsyncCall(responseCallback))
Create an AsyncCall. This AsyncCall is actually a Runnable, which can be regarded as a task we requested. The dispatcher.enqueue method is to put the task into the waiting queue.
// dispatcher class internal fun enqueue(call: AsyncCall) { synchronized(this) { //Put the task into the waiting queue readyAsyncCalls.add(call) // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (!call.call.forWebSocket) { val existingCall = findExistingCallWithHost(call.host) if (existingCall != null) call.reuseCallsPerHostFrom(existingCall) } } //Start processing the task promoteAndExecute() }
// dispatcher class private fun promoteAndExecute(): Boolean { this.assertThreadDoesntHoldLock() val executableCalls = mutableListOf<AsyncCall>() val isRunning: Boolean synchronized(this) { val i = readyAsyncCalls.iterator() while (i.hasNext()) { val asyncCall = i.next() //The number of requests is greater than or equal to 64, end the loop if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity. // The same host is greater than or equal to 5, end the loop if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity. //Remove from waiting queue i.remove() asyncCall.callsPerHost.incrementAndGet() executableCalls.add(asyncCall) //Put the task into the run queue runningAsyncCalls.add(asyncCall) } isRunning = runningCallsCount() > 0 } for (i in 0 until executableCalls.size) { val asyncCall = executableCalls[i] // This method is to put our tasks into the thread pool and hand them over to the thread pool for processing asyncCall.executeOn(executorService) } return isRunning }
//AsyncCall class fun executeOn(executorService: ExecutorService) { client.dispatcher.assertThreadDoesntHoldLock() var success = false try { //Put the task into the thread pool executorService.execute(this) success = true } catch (e: RejectedExecutionException) { val ioException = InterruptedIOException("executor rejected") ioException.initCause(e) noMoreExchanges(ioException) responseCallback.onFailure(this@RealCall, ioException) } finally { if (!success) { client.dispatcher.finished(this) // This call is no longer running! } } }
Then the run method of AsyncCall will be executed.
override fun run() { threadName("OkHttp ${redactedUrl()}") { var signaledCallback = false timeout.enter() try { // Use the five interceptors provided by default to redirect, configure request headers, cache, establish connections, and make network calls to services val response = getResponseWithInterceptorChain() signaledCallback = true // Callback will call back the response body we got responseCallback.onResponse(this@RealCall, response) } catch (e: IOException) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e) } else { // Error message callback responseCallback.onFailure(this@RealCall, e) } } catch (t: Throwable) { cancel() if (!signalledCallback) { val canceledException = IOException("canceled due to $t") Exception.addSuppressed(t) canceled responseCallback.onFailure(this@RealCall, canceledException) } throw t } finally { client.dispatcher.finished(this) } } } }
At this point we have completed the post request
The get request process is simpler than post
// newCall is the same as post, mainly depends on execute val response= client.newCall(request).execute()
// override fun execute(): Response { check(executed.compareAndSet(false, true)) { "Already Executed" } timeout.enter() callStart() try { // this is the call that puts the task into the runningSyncCalls queue through our dispatcher client.dispatcher.executed(this) // Get the response data of our service through the default five interceptors return getResponseWithInterceptorChain() } finally { client.dispatcher.finished(this) } }