current position:Home>[knowledge points] okhttp principle 8 questions

[knowledge points] okhttp principle 8 questions

2022-01-26 21:51:19 The code is not difficult to write

Preface

OkHttp Can be said to be Android The most common network request framework in development ,OkHttp Easy to use , Extensibility is strong , Powerful ,OKHttp Source code and principle are also frequent visitors in the interview
however OKHttp The content of the source code is more , Want to learn its source code is often a myriad of things , I can't grasp the key point at the moment .
This paper starts from several problems OKHttp Related knowledge , In order to quickly build OKHttp Knowledge system , If it works for you , Welcome to thumb up ~

This article mainly includes the following contents

  1. OKHttp What is the overall process of the request ?
  2. OKHttp How the dispenser works ?
  3. OKHttp How the interceptor works ?
  4. What is the difference between application interceptor and network interceptor ?
  5. OKHttp How to reuse TCP Connect ?
  6. OKHttp How to clear idle connections ?
  7. OKHttp What are the advantages ?
  8. OKHttp What design patterns are used in the framework ?

1. OKHttp Introduction to the overall request process

First, let's look at the simplest Http How is the request sent .

   val okHttpClient = OkHttpClient()
   val request: Request = Request.Builder()
       .url("https://www.google.com/")
       .build()

   okHttpClient.newCall(request).enqueue(object :Callback{
       override fun onFailure(call: Call, e: IOException) {
       }

       override fun onResponse(call: Call, response: Response) {
       }
   })

This code looks simple ,OkHttp At least only contact is required during the request process OkHttpClientRequestCallResponse, However, there will be a lot of logical processing inside the framework .
Most of the logic of all network requests is concentrated in interceptors , But before entering the interceptor, you need to rely on the distributor to allocate the request task .
About distributors and interceptors , Let's briefly introduce , There will be more detailed explanations later

  • The dispenser : Internally maintain queues and thread pools , Complete the requested deployment ;
  • Interceptor : The five default interceptors complete the whole request process .

image

The whole network request process is roughly as shown above

  1. Build... Through the builder model OKHttpClient And Request
  2. OKHttpClient adopt newCall Initiate a new request
  3. Maintain the request queue and thread pool through the distributor , Complete the requested deployment
  4. Complete the request and retry through the five default interceptors , Cache handling , A series of operations such as establishing a connection
  5. Get network request results

2. OKHttp How the dispenser works ?

The main function of the distributor is to maintain the request queue and thread pool , For example, we have 100 Asynchronous requests , They must not be requested at the same time , Instead, they should be queued into categories , It is divided into the list in request and the list waiting , When the request is completed , You can take the waiting request from the waiting list , To complete all requests

Here, synchronous requests are slightly different from asynchronous requests

Synchronization request

synchronized void executed(RealCall call) {
	runningSyncCalls.add(call);
}

Because synchronous requests do not require a thread pool , There are no restrictions . So the distributor just makes a record . The subsequent requests can be synchronized in the order of joining the queue

Asynchronous requests

synchronized void enqueue(AsyncCall call) {
	// The maximum number of requests cannot exceed 64, same Host Request cannot exceed 5 individual 
	if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) 	  {
		runningAsyncCalls.add(call);
		executorService().execute(call);
	} else {
		readyAsyncCalls.add(call);
	}
}

When the task being executed does not exceed the maximum limit 64, Same at the same time Host Your request does not exceed 5 individual , Will be added to the executing queue , Submit to the thread pool at the same time . Otherwise, join the waiting queue first .
After each mission , Will call the distributor finished Method , This will take out the tasks in the waiting queue and continue to execute

3. OKHttp How the interceptor works ?

Task distribution through the distributor above , Next, we will start a series of configurations with the interceptor

# RealCall
  override fun execute(): Response {
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }

Let's see RealCall Of execute Method , It can be seen that , Finally, I return to getResponseWithInterceptorChain, The construction and handling of responsibility chain is actually in this method

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
    )
    val response = chain.proceed(originalRequest)
  }

As shown above , Built a OkHttp The interceptor's chain of responsibility
Responsibility chain , seeing the name of a thing one thinks of its function , It is an execution chain used to deal with related affairs , There are multiple nodes on the execution chain , Every node has a chance ( Matching conditions ) Processing request transactions , If a node has finished processing, it can be transferred to the next node to continue processing or return to the end of processing according to the actual business needs .
As shown above, the order and function of adding responsibility chain are shown in the following table :

Interceptor effect
Apply interceptors Got the original request , You can add some custom header、 General parameters 、 Parameter encryption 、 Gateway access, etc .
RetryAndFollowUpInterceptor Handle error retries and redirects
BridgeInterceptor Bridging interceptors at application layer and network layer , The main work is to add cookie、 Add fixed header, such as Host、Content-Length、Content-Type、User-Agent wait , Then save the results of the response cookie, If the response uses gzip Compression , You also need to decompress .
CacheInterceptor Cache interceptor , If the cache is hit, the network request will not be initiated .
ConnectInterceptor Connection interceptor , A connection pool is maintained internally , Responsible for connection reuse 、 Create connection ( Three handshakes, etc )、 Release the connection and create the connection socket flow .
networkInterceptors( Network interceptor ) User defined interceptors , It is usually used to monitor the data transmission at the network layer .
CallServerInterceptor Request interceptor , After pre preparation , Actually initiated a network request .

In this way, our network requests pass through the responsibility chain and pass down level by level , Eventually it will be executed CallServerInterceptor Of intercept Method , This method encapsulates the result of the network response into a Response Object and return. Then go back level by level along the responsibility chain , Finally back to getResponseWithInterceptorChain Method return , As shown in the figure below :

4. What is the difference between application interceptor and network interceptor ?

From the whole responsibility link , The application interceptor is the first interceptor to execute , That is, the user sets it himself request Original request after property , The network interceptor is located in ConnectInterceptor and CallServerInterceptor Between , At this point, the network link is ready , Just wait to send request data . They mainly have the following differences

  1. First , The application interceptor is in RetryAndFollowUpInterceptor and CacheInterceptor Before , So in case of an error, try again or network redirection , The network interceptor may execute multiple times , Because it's equivalent to making a second request , But the application interceptor will always trigger only once . And if it's in CacheInterceptor If you hit the cache, you don't need to go through the network request , Therefore, there will be a short circuit network interceptor .
  2. secondly , except CallServerInterceptor outside , Each interceptor should call... At least once realChain.proceed Method . In fact, in the application interceptor layer, you can call proceed Method ( Local exception retry ) Or not proceed Method ( interrupt ), But the network interceptor layer connection is ready , Can and can only be called once proceed Method .
  3. Last , From the use scenario , Apply the interceptor because it will only be called once , It is usually used to count the initiation of network requests from clients ; One call of the network interceptor means that a network communication will be initiated , Therefore, it can usually be used to count the data transmitted on the network link .

5. OKHttp How to reuse TCP Connect ?

ConnectInterceptor My main job is to establish TCP Connect , establish TCP The connection requires three handshakes and four waves , If each HTTP All requests need to create a new TCP It consumes more resources
and Http1.1 Has supported keep-alive, That is many Http Request to reuse one TCP Connect ,OKHttp We have also made corresponding optimization , So let's see OKHttp How to reuse TCP Connected

ConnectInterceptor The code to find the connection in will eventually call ExchangeFinder.findConnection Method , As follows :

# ExchangeFinder
// To host a new data stream   seek   Connect . The search order is   Assigned connections 、 Connection pool 、 make new connection 
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
  synchronized (connectionPool) {
    // 1. Try to use   Connection assigned to data flow .( For example, when redirecting a request , The last requested connection can be reused )
    releasedConnection = transmitter.connection;
    result = transmitter.connection;

    if (result == null) {
      // 2.  There are no assigned connections available , Just try to get... From the connection pool .( Connection pooling will be explained in detail later )
      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
        result = transmitter.connection;
      }
    }
  }

  synchronized (connectionPool) {
    if (newRouteSelection) {
      //3.  There is now a IP Address , Try again to get... From the connection pool . May match due to connection merging .( Here comes routes, From above null)
      routes = routeSelection.getAll();
      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {
        foundPooledConnection = true;
        result = transmitter.connection;
      }
    }

  // 4. The second time didn't succeed , Just put the new connection , Conduct TCP + TLS  handshake , Establish a connection with the server .  Blocking operation 
  result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
      connectionRetryEnabled, call, eventListener);

  synchronized (connectionPool) {
    // 5.  The last attempt to get from the connection pool , Note that the last parameter is true, I.e. requirement   Multiplexing (http2.0)
    // intend , If this time is http2.0, So to make sure   Multiplexing ,( Because the above handshake operation is not thread safe ) It will confirm again whether the same connection exists in the connection pool at this time 
    if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
      //  If you get , Just close the connection we created , Return the obtained connection 
      result = transmitter.connection;
    } else {
      // If there's no last attempt , Just save the newly created connection into the connection pool 
      connectionPool.put(result);
    }
  }
 
  return result;
}

Some of the code has been simplified above , It can be seen that , The connection interceptor uses 5 There are two ways to find connections

  1. First try to use The connection assigned to the request .( A situation where a connection has been allocated, such as a re request during redirection , It indicates that there was a connection last time )
  2. If there is no Assigned available connections , Just try from the connection pool Match to get . Because there is no routing information at this time , So matching conditions :address Agreement ——hostport、 Agent, etc , And the matching connection can accept new requests .
  3. If no... Is obtained from the connection pool , Then in routes Try again to get , This is mainly aimed at Http2.0 An operation of ,Http2.0 You can reuse square.com And square.ca The connection of
  4. If you don't get it the second time , create RealConnection example , Conduct TCP + TLS handshake , Establish a connection with the server .
  5. At this time, in order to ensure Http2.0 Multiplexing of connections , Will match from the connection pool for the third time . Because the handshake process of the newly established connection is non thread safe , Therefore, at this time, the same connection may be newly stored in the connection pool .
  6. The third time, if it matches , Use the existing connection , Release the newly created connection ; If it does not match , Save the new connection into the connection pool and return .

The above is the operation of the connection interceptor trying to reuse the connection , The flow chart is as follows :

6. OKHttp How to clear idle connections ?

It says we'll build one TCP Connection pool , But if there's no mission , Idle connections should also be cleared in time ,OKHttp how ?

  # RealConnectionPool
  private val cleanupQueue: TaskQueue = taskRunner.newQueue()
  private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
    override fun runOnce(): Long = cleanup(System.nanoTime())
  }

  long cleanup(long now) {
    int inUseConnectionCount = 0;// Number of connections in use 
    int idleConnectionCount = 0;// Number of idle connections 
    RealConnection longestIdleConnection = null;// The connection with the longest idle time 
    long longestIdleDurationNs = Long.MIN_VALUE;// The longest free time 

    // Traversal connection : Find the connection to be cleaned ,  Find the time for the next cleanup ( It's not the maximum free time yet )
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use ,continue, Number of connections in use +1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
		// Number of idle connections +1
        idleConnectionCount++;

        //  Assign the longest idle time and the corresponding connection 
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
	  // If the longest idle time is greater than 5 minute   or   Free number   Greater than 5, Just remove and close the connection 
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        // else, Just go back to   How long will it take to arrive 5 minute , then wait This time to clean up 
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // There are no idle connections , Just 5 Try cleaning again in minutes .
        return keepAliveDurationNs;
      } else {
        //  No connection , Don't clean up 
        cleanupRunning = false;
        return -1;
      }
    }
	// Close the removed connection 
    closeQuietly(longestIdleConnection.socket());

    // After closing and removing   immediately   Carry on the next   Try to clean up 
    return 0;
  }

The idea is still very clear :

  1. When the connection is added to the connection pool, the scheduled task will be started
  2. If there is a free connection , If the maximum idle time is greater than 5 minute or Free number Greater than 5, Remove and close the longest idle connection ; If Free number No more than 5 And The longest idle time is no more than 5 minute , Just go back to 5 Minutes left , Then wait for this time to clean up .
  3. No idle connection, just wait 5 Try cleaning again in minutes .
  4. No connection, no cleaning .

The flow is shown in the following figure :

7. OKHttp What are the advantages ?

  1. Easy to use , The appearance mode is used in the design , Hide the complexity of the whole system , Interface the subsystem through a client OkHttpClient Unity is exposed .
  2. Extensibility is strong , You can customize the application interceptor and network interceptor , Complete various user-defined requirements
  3. Powerful , Support SpdyHttp1.XHttp2、 as well as WebSocket Other protocols
  4. Reuse the bottom layer through connection pool TCP(Socket), Reduce request latency
  5. Seamless support GZIP Reduce data traffic
  6. Support for data caching , Reduce duplicate network requests
  7. Support other functions of the host that automatically retry when the request fails ip, Automatic redirection

8. OKHttp What design patterns are used in the framework ?

  1. Builder model :OkHttpClient And Request The builder pattern is used in the construction of
  2. Appearance mode : OkHttp Using the appearance mode , Hide the complexity of the whole system , Interface the subsystem through a client OkHttpClient Unity is exposed .
  3. The chain of responsibility model : OKHttp The core of is the responsibility chain model , adopt 5 The responsibility chain composed of two default interceptors completes the configuration of the request
  4. The flyweight pattern : The core of the sharing mode is reuse in the pool ,OKHttp Reuse TCP Connection pool is used in connection , At the same time, thread pool is also used in asynchronous requests

summary

This article mainly combs OKHttp Principle related knowledge points , And answered the following questions :

  1. OKHttp What is the overall process of the request ?
  2. OKHttp How the dispenser works ?
  3. OKHttp How the interceptor works ?
  4. What is the difference between application interceptor and network interceptor ?
  5. OKHttp How to reuse TCP Connect ?
  6. OKHttp How to clear idle connections ?
  7. OKHttp What are the advantages ?
  8. OKHttp What design patterns are used in the framework ?

If it helps you , Welcome to thumb up , thank you ~

In this paper, from https://juejin.cn/post/7020027832977850381, If there is any infringement , Please contact to delete .

copyright notice
author[The code is not difficult to write],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201262151176452.html

Random recommended