Introduction to OkHttp

OkHttp, an open source project for handling web requests, is android’s hottest lightweight framework, contributed by Square (the company also contributed Picasso)

Two, OKHttp advantages

  1. Get and POST requests are supported
  2. Supports http-based file upload and download
  3. Support for loading images
  4. Supports GZIP compression
  5. Support for response caching to avoid repeated network requests
  6. Support for connection pooling to reduce response latency issues.

Introduction to OkHttp

  1. Create an OkHttpClien object
  2. Create a Request object that is generated by calling the inner class Builder
  3. Create a Call object that calls execute(synchronous request)/ enQueue (asynchronous request)

Example code 1. execute(synchronize get requests)

public void getSyncRequest(View view) { // 1. OkHttpClient client = new OkHttpClient(); Final Request Request = new request.builder ().get().url(" HTTPS :www.baidu.com").build(); // 3. Encapsulate Request as Call final Call Call = client.newCall(Request); New Thread(new Runnable() {@override public void run() {try { Response Response = call.execute(); Log.d(TAG, "response.code() = " + response.code() + ", response.message() = " + response.message() + ", response.body() = " + response.body().string() + ""); } catch (IOException e) { Log.d(TAG, "" + e + ""); e.printStackTrace(); } } }).start(); }Copy the code

1. Execute (asynchronous GET request)

public void getAsyncRequest(View view) { // 1. OkHttpClient client = new OkHttpClient(); Final Request Request = new request.builder ().get().url(" HTTPS :www.baidu.com").build(); // 3. Encapsulate Request as Call final Call Call = client.newCall(Request); Call.enqueue (new Callback() {@override public void onFailure(@notnull call call, @NotNull IOException e) { Log.d(TAG, "get failed" + ""); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { textView.setText(res); }}); }}); }Copy the code

A post request

public void postFormRequest(View view) { // 1. OkHttpClient client = new OkHttpClient(); // 2. Build FormBody, FormBody FormBody = new formBody.builder ().add("username", "admin").add("username", "admin").build(); // 3. Construct Request, Pass FormBody as an argument to the Post method in final Request Request = new Request.builder ().url("http://www.jianshu.com").post(FormBody) .build(); Call = client.newCall(Request); // 5. Call request, Call.enqueue (new Callback() {@override public void onFailure(@notnull call call, @NotNull IOException e) { Log.d(TAG, "get failed" + ", e.getMessage() = " + e.getMessage() + ", e = " + e + ""); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { textView.setText(res); }}); }}); }Copy the code

4. OkHttp source code Analysis (Kotlin)

This factory will be used to send Http requests and read their returns. The best way to use OkHttp is to create a singleton instance of OkHttpClient and reuse it. This is because each Client has its own connection pool and thread pools. Reusing these connection and thread pools can reduce latency and save memory.

OkHttpClient client = new OkHttpClient(); Final Request Request = new request.builder ().get().url(" HTTPS :www.baidu.com").build(); // 3. Encapsulate Request as Call final Call Call = client.newCall(Request);Copy the code

Look at the newCall method

  /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
Copy the code

RealCall is a Call implementation class. RealCall implements methods such as Execute and enQueue. Look at the execute method of the Real method

  override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
Copy the code

The synchronous method does four things

  1. Check to see if the call has already been executed. Each call can only be executed once. If you want an exact call, you can clone it using the call#clone method.
  2. Executed using client.dispatcher().executed(this). The Dispatcher is a member of the okHttpClient.Builder that you just saw. Its document says it is the execution policy for asynchronous HTTP requests. Synchronous requests are also involved.
  3. Call getResponseWithInterceptorChain () function to obtain the HTTP return a result, can be seen from the function name, this step will make a series of “intercept” operation.
  4. Finally, inform the Dispatcher that they have done their job.

Here the request is synchronized, just adding the current request to the queue

  /** Used by [Call.execute] to signal it is in-flight. */
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }
Copy the code

Real network requests, parsing results returned, or getResponseWithInterceptorChain, said this below, and then call the dispatch. The method to finish

Let’s look at the asynchronous request RealCall.enqueue

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
Copy the code

Call dispatch.enqueue to determine whether the Call is currently executing

internal fun enqueue(call: AsyncCall) { synchronized(this) { 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) } } promoteAndExecute() }Copy the code

The call is in the promoteAndExecute method

  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()

        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)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
Copy the code

In the promoteAndExecute method, there is a judgment that if the currently running asynchronous request queue length is less than maxRequests, If the number of host requests is less than the number of requests per host, maxRequestsPerHost is 5, the current request is added to the run queue and assigned to the thread pool ExecutorService. Otherwise, it is placed in readAsyncCall for caching and waiting to be executed. You can see one difference between synchronous and asynchronous is that asynchronous execution is handed over to the thread pool.

Take a look at the thread pool ExecutorService within OkHttp

  @get:Synchronized
  @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!!
    }
Copy the code

In this case, thread pools are created using ThreadPoolExecutor

Dispatch. enqueue method d AsyncCall, which inherits from NamedRunnable, and NamaedRunnable, which implements the Runnable method

/** * Attempt to enqueue this async call on [executorService]. This will attempt to clean up * if the executor has been shut down by reporting the call as failed. */ fun executeOn(executorService: ExecutorService) { client.dispatcher.assertThreadDoesntHoldLock() var success = false try { 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! } } } override fun run() { threadName("OkHttp ${redactedUrl()}") { var signalledCallback = false timeout.enter() try { val response = getResponseWithInterceptorChain() signalledCallback = true 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 { responseCallback.onFailure(this@RealCall, e) } } catch (t: Throwable) { cancel() if (! signalledCallback) { val canceledException = IOException("canceled due to $t") canceledException.addSuppressed(t) responseCallback.onFailure(this@RealCall, canceledException) } throw t } finally { client.dispatcher.finished(this) } } } }Copy the code

You can see that in its constructor, Callback is what we set up to create, with onFail and onResponse methods. The thread pool ends up calling our Runnable.

Here by

val response = getResponseWithInterceptorChain()

Method to access the connection, as in synchronous requests. According to the return value to invoke the callback. The onFailure/onResponse

Let’s look at how the OkHttp connection returns, and let’s look at this method, okay

@Throws(IOException::class) internal fun getResponseWithInterceptorChain(): Response { // Build a full stack of interceptors. val interceptors = mutableListOf<Interceptor>() interceptors += client.interceptors interceptors += RetryAndFollowUpInterceptor(client) interceptors += BridgeInterceptor(client.cookieJar) interceptors += CacheInterceptor(client.cache) interceptors += ConnectInterceptor if (! forWebSocket) { interceptors += client.networkInterceptors } interceptors += CallServerInterceptor(forWebSocket) 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 { val response = chain.proceed(originalRequest) if (isCanceled()) { response.closeQuietly() throw IOException("Canceled") } return response } catch (e: IOException) { calledNoMoreExchanges = true throw noMoreExchanges(e) as Throwable } finally { if (! calledNoMoreExchanges) { noMoreExchanges(null) } } }Copy the code

As you can see, it’s all about the use of the Interceptor

  • RetryAndFollowUpInterceptor: create StreamAllocation object, handle HTTP redirection, error retry. Impact on subsequent Interceptor execution: Modify request and StreamAllocation.

  • BridgeInterceptor: Complete some missing HTTP headers and Cookie Settings. Impact on subsequent Interceptor execution: Modify request.

  • CacheInterceptor: Handles HTTP caching. Impact on subsequent Interceptor execution: If the requested response is in the cache, subsequent interceptors will not execute it.

  • ConnectInterceptor: Establishes a connection to the server (in the newStream method) using the StreamAllocation object previously allocated, and selects HTTP 1.1 or HTTP 2 for the interaction. Impact on subsequent Interceptor execution: httpStream and Connection are created.

  • CallServerInterceptor: Processes I/O and exchanges data with the server. Impact on subsequent Interceptor execution: It is the last Interceptor in the Interceptor chain, and there are no subsequent interceptors.

Project code

OkHttp usage details OkHttp parse (a) from the usage see principle