What is OkHttp?

Introduction to the

OkHttp is an excellent HTTP framework that supports GET and POST requests, HTTP-based file uploads and downloads, image loading, transparent GZIP compression of downloaded files, response caching to avoid repeated network requests, and connection pooling to reduce response latency. Developed by Square, OkHttp is currently one of the most popular web frameworks for Android.

Official website: OKHttp website

Making address: dead simple

The characteristics of

  1. Support HTTP2 / SPDY
  2. The socket automatically selects the best route and supports automatic reconnection
  3. Automatic maintenance of the socket connection pool to reduce the number of handshakes
  4. Have a queue thread pool, easy to write concurrency
  5. Having Interceptors easily handle requests and responses (such as transparent GZIP compression) based on Headers caching strategies

How do I use OkHttp?

1, gradle import library, implementation ‘com. Squareup. Okhttp3: okhttp: 3.11.0’

Initialize the OkHttpClient object

 client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(15, TimeUnit.SECONDS)
                .build();
Copy the code

A synchronous request

public void okHttpSync() {
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            if (response.isSuccessful()) {
                System.out.println("response.code()==" + response.code());
                System.out.println("response.heard()==" + response.headers());
                System.out.println("response.message()==" + response.message());
                System.out.println("res=="+ response.body().string()); } } catch (IOException e) { e.printStackTrace(); }}Copy the code

An asynchronous request

    public void okHttpAsync() {
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                needCancelled.set(true);
                System.out.println("url==" + call.request().url());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    System.out.println("response.code()==" + response.code());
                    System.out.println("response.heard()==" + response.headers());
                    System.out.println("response.message()==" + response.message());
                    System.out.println("res==" + response.body().string());
                    needCancelled.set(true); }}}); }Copy the code

For details on OkHttp usage, see OkHttp usage

What is the OkHttp core execution flow?

Description of key class functions

class Functional specifications
OKHttpClient It contains a lot of objects, many of the function modules of OKhttp are wrapped in this class, let this class alone to provide external API, using the Builder model
The Request and Response Abstract network input and response model
Call HTTP request task encapsulation is an interface
RealCall The realization of the Call, implement the execute () synchronous method, the enqueue (Callback responseCallback) asynchronous method, getResponseWithInterceptorChain interceptor response ()
AsyncCall An inner class for RealCall. Inheriting the Runnable interface, it is then executed in an asynchronous thread pool
Dispatcher Core scheduling class, internal maintenance for readyAsyncCalls, runningAsyncCalls, runningSyncCalls queue, the actual RealCall is also to call this class for synchronization, asynchronous concrete implementation. A thread pool is maintained internally with a maximum concurrency limit of maxRequests=64.
RealInterceptorChain The chain of interceptors, which maintains a queue of interceptors, executes the intercept method of the next interceptor each time proceed passes index + 1
RetryAndFollowUpInterceptor Responsible for failed reconnection and redirection
BridgeInterceptor Process Request and Response packets
CacheInterceptor Responsible for caching interceptors
ConnectInterceptor Responsible for maintaining the connection interceptor
CallServerInterceptor Responsible for the last network IO read and write

Code execution flow

1. Unified construction of OkHttpClient object through Builder mode

2. Through Call, RealCall can be used to send requests

RealCall invokes the Execute () and enqueue() methods of the Dispatcher for synchronous and asynchronous requests

4, the final call ReallCall getResponseWithInterceptorChain () method to intercept chain block

5. Redirection interceptor, bridge interceptor, cache interceptor, connection interceptor, network interceptor are processed in sequence

6. Finally, Response is returned via Intercept’s return, and the result of the request is finally returned to the client

How does OkHttp manage thread scheduling?

Thread scheduling

A thread pool is maintained in the Dispatcher to which asynchronous requests add tasks.

 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
    }
    return executorService;
  }
Copy the code

The default maximum number of concurrent requests is maxRequests=64. If you exceed this limit, you will be added to the waiting queue

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else{ readyAsyncCalls.add(call); }}Copy the code

Finally, the thread pool executes the execute() method in AsyncCall, as follows

   @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else{ eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); }}Copy the code

The queue mechanism

Three queues are maintained in Dispathcer, namely asynchronous wait queue, asynchronous execution queue and synchronous execution queue.

 /** Ready async calls in the order they'll be run. */ private final Deque
      
        readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven'
      t finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque
      
        runningSyncCalls = new ArrayDeque<>();
      Copy the code

Whether synchronous or asynchronous, the Dispatcher finished method is called in the finally block, which removes the queue task

 int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if(! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if(runningCallsCount == 0 && idleCallback ! = null) { idleCallback.run(); }Copy the code

The promoteCalls method is called again in Finish, which retrieves the prepared queue and adds it to the thread

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
Copy the code

How does OkHttp’s interceptor and call chain work?

The call chain executes the process

Through the above analysis, we know that no matter synchronous or asynchronous, final call to are RealCall getResponseWithInterceptorChain () method, as follows:

Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor>  interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client));if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
Copy the code

The “proceed” method is used to define the interceptor collection and the RealInterceptorChain.

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if(this.httpCodec ! = null && ! this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if(this.httpCodec ! = null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if(httpCodec ! = null && index + 1 < interceptors.size() && next.calls ! = 1) { throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response; }Copy the code

1. Check whether the size of the list is exceeded. If so, the traversal ends

2, calls + 1

New RealInterceptorChain, then index+1

Get the next interceptor object from the list

5. Execute the Interceptor intercept method

One RealInterceptorChain for each RealInterceptorChain, and one RealInterceptorChain for each RealInterceptorChain until the List iteration is complete.

The interceptor

It can be seen from the above invocation relationship that all interceptors are provided by the system except the interceptors circled in red. This whole process is a recursive execution process. After getting the final Response from the CallServerInterceptor, the Response is returned recursively step by step. It passes NetworkInterceptor and finally reaches Application Interceptor.

How does OkHttp cache data?

Caching strategies

OkHttp uses the CacheInterceptor interceptor to control data caching using the CacheStrategy to implement the above flowchart, which policies the results of a previously cached Request against the headers that are currently being sent, and determines whether or not a Request should be made. Based on whether the networkRequest and cacheResponse values are null, different policies are given as follows:

networkRequest cacheResponse Result the results
null null Only -if-cached (503 error must be returned if no network request is made and the cache does not exist or is out of date)
null non-null Does not make network request, returns cache directly, does not request network
non-null null Need to make a network request, and cache does not exist or past, direct access to the network
non-null non-null The Header contains the ETag/ last-Modified tag. The request needs to be made if the condition is met, or the network needs to be accessed

Caching algorithm

By analyzing the CacheInterceptor interceptor’s Intercept method, we can see that the Cache class is used for caching, which is implemented in the DiskLruCache class. Caching is actually a complex logic, a separate function block that is not actually part of OKhttp, but is actually handled by HTTP and DiskLruCache. LinkedHashMap implements the LRU algorithm and in this case is used as an in-memory index to DiskCache

If you are interested, you can refer to the concrete implementation of the following 2 articles:

OKHttp source code parsing (6)– Middle level caching basics

OKHttp source code parsing (7)- intermediate caching mechanism

What is the connection pool reuse mechanism for OkHttp?

link

RealConnection is the realization class of Connection, representing the link to the socket. If we have a RealConnection, it means that we have a communication link with the server, and through RealConnection, it means to connect the socket link. The RealConnection object means that we have a communication link with the server. In addition to the bridge StreamAllocation class for streaming, initialized in RetryAndFollowUpInterceptor, in ConnectInterceptor newStream operation, specific connection interceptor code is as follows:

 public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = ! request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
Copy the code

The findConnection method is called at the end of the newStream creation process, which is the key to connection reuse. If a connection can be reused from the connection pool, it will be returned directly. Otherwise, add RealConnection to ConnectionPool as follows:

 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if(codec ! = null) throw new IllegalStateException("codec ! = null");
      if (canceled) throw new IOException("Canceled");

      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if(this.connection ! = null) { // We had an already-allocated connection and it's good. result = this.connection; releasedConnection = null; } if (! reportedAcquired) { // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      if (result == null) {
        // Attempt to get a connection from the pool.
        Internal.instance.get(connectionPool, address, this, null);
        if(connection ! = null) { foundPooledConnection =true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if(releasedConnection ! = null) { eventListener.connectionReleased(call, releasedConnection); }if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if(result ! = null) { // If we found an already-allocated or pooled connection, we're done. return result; } // If we need a route selection, make one. This is a blocking operation. boolean newRouteSelection = false; if (selectedRoute == null && (routeSelection == null || ! routeSelection.hasNext())) { newRouteSelection = true; routeSelection = routeSelector.next(); } synchronized (connectionPool) { if (canceled) throw new IOException("Canceled"); if (newRouteSelection) { // Now that we have a set of IP addresses, make another attempt at getting a connection from // the pool. This could match due to connection coalescing. List
      
        routes = routeSelection.getAll(); for (int i = 0, size = routes.size(); i < size; i++) { Route route = routes.get(i); Internal.instance.get(connectionPool, address, this, route); if (connection ! = null) { foundPooledConnection = true; result = connection; this.route = route; break; } } } if (! foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); } // Create a connection and assign it to this allocation immediately. This makes it possible // for an asynchronous cancel() to interrupt the handshake we'
      re about to do.
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done. if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; } // Do TCP + TLS handshakes. This is a blocking operation. result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; // Pool the connection. Internal.instance.put(connectionPool, result); // If another multiplexed connection to the same address was created concurrently, then // release this connection and acquire that one. if (result.isMultiplexed()) { socket = Internal.instance.deduplicate(connectionPool, address, this); result = connection; } } closeQuietly(socket); eventListener.connectionAcquired(call, result); return result; }Copy the code

The connection pool

ConnectionPool is used in OkHttp to manage HTTP and HTTP /2 links to reduce network request latency. The same address shares the same connection. This class achieves the goal of multiplexing connections. An OkHttpClient contains only one ConnectionPool. Instantiation is also done in OkHttpClient. ConnectionPool method calls are not exposed directly. Instead, they are exposed through OkHttpClient’s Internal interface.

1. Use the get method to obtain the connection, or check whether there is a suitable link, otherwise return null, implement as follows:

 RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        returnconnection; }}return null;
  }
Copy the code

2, Join the connection using the put method, which will trigger cleanupRunnable to clean the connection. The concrete implementation is as follows:

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if(! cleanupRunning) { cleanupRunning =true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
Copy the code

3. Specific connection recycling mechanism: Firstly count the number of idle connections, and then search the connection with the longest idle time and its corresponding idle time through the for loop. Then determine whether the maximum number of idle connections (maxIdleConnections) or keepAliveDurationNs (keepAliveDurationNs) is exceeded, and clear the connections with the maximum idle duration. If the cleanup condition is not met, a corresponding wait time is returned. The specific implementation is as follows:

long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use, keep searching.
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }

        idleConnectionCount++;

        // If the connection is ready to be evicted, we're done. long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return- 1; } } closeQuietly(longestIdleConnection.socket()); // Cleanup again immediately.return 0;
  }
Copy the code

What is the underlying network implementation of OkHttp?

OkHttp uses okIO to perform IO operations. Developed by Square, Okio complements java.io and java.nio to make it easier and faster to access, store, and process your data. This library is also used for the OKHttp underlying support. Okio is also very simple to use, eliminating much of the basic code for IO operations, and optimizing memory and CPU usage.

2, do not rely on other Http implementation of the library, the underlying use of Socket, their own implementation of http1. X and 2.X protocol.

What design patterns are used in OkHttp code? What are the clever ones?

1. Builder mode

For OkHttpClient object creation, Request object creation, and Respone object creation, the Builder pattern is used to unify complex object creation in different methods, making the creation process easier.

2, appearance mode OkHttpClient provides unified external scheduling, shielding the internal implementation, making the use of the network library simple and convenient.

The interceptor in OkHttp uses the responsibility chain mode, which dynamically forms the invocation form of the chain by implementing different interceptors independently. Responsibilities are clear and dynamically extensible.

Why OkHttp?

At present, Android development, the main network framework has HttpClient, Volley, HttpURLConnection, OkHttp.

Android has not recommended httpClient, 5.0 simply scrapped, 6.0 removed httpClient. So HttpClient doesn’t care. The Volley framework is no longer updated, so we are currently considering using HttpURLConnection and OkHttp.

Compared with HttpURLConnection, OkHttp is more convenient and flexible to use, and the third-party community is active, relevant information is complete, mature and stable. OkHttp is also officially recognized and is constantly being optimized and updated, so it is recommended that applications choose OkHttp as a network framework first.

conclusion

thinking

In the development process of the project, we often use a large number of third-party frameworks, but may not know why, through continuous thinking to ask why, so as to study the implementation of the source code. It allows us to use the framework more freely and solve some underlying problems.

The resources

OKHttp usage details

OKHTTP analyzes the differences between the two custom interceptors with examples from the official website

OKHttp source code parsing (a) series

recommended

Android source code series – decrypt OkHttp

Android source code series – Decrypt Retrofit

Android source code series – Decrypt Glide

Android source code series – Decrypt EventBus

Android source code series – decrypt RxJava

Android source code series – Decrypt LeakCanary

Android source code series – decrypt BlockCanary

about

Welcome to pay attention to my personal public number

Wechat search: Yizhaofusheng, or search the official ID: Life2Code

  • Author: Huang Junbin
  • Blog: junbin. Tech
  • GitHub: junbin1011
  • Zhihu: @ JunBin