Android source code - an article takes you to understand OkHttp. It's too complete

Keywords: Android Design Pattern

     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);
  }
}
# Queue mechanism

Dispathcer Three queues are maintained in, namely asynchronous waiting 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 runningAsyncCalls = new ArrayDeque<>();

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

Whether synchronous or asynchronous, in the end finally Blocks are called dispatcher of finished Method, the queue task will be removed, and the final implementation is as follows

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();
}
stay finish Will be called again in promoteCalls Method, the queue in preparation will be retrieved again and added 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.
}

}

# How does OkHttp's interceptor and call chain execute?

# Call chain execution process

Through the above analysis, we know that whether synchronous or asynchronous, the final call is RealCall of getResponseWithInterceptorChain()The method is as follows:

Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List 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);

}

It defines the interceptor set and RealInterceptorChain Interception chain, which specifically implements the implementation of the interception chain proceed The method is as follows:

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;

}

1.  First judge whether it exceeds list of size,If it exceeds, the traversal ends. If it does not exceed, the execution continues
2.  calls+1
3.  new One RealInterceptorChain,Where then subscript index+1
4.  from list Take out the next one interceptor object
5.  implement interceptor of intercept method

To sum up, each RealInterceptorChain Corresponding to one interceptor,Then each interceptor Generate the next one RealInterceptorChain,until List Iteration complete.

# Interceptor

![](https://upload-images.jianshu.io/upload_images/22861276-422d97c744b04f33?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

It can be seen from the above call relationship that except the interceptors circled in red, they are all interceptors provided by the system. The whole process is a recursive execution process. In CallServerInterceptor Get the final Response After that, the response Return step by step recursively, and the period will pass NetworkInterceptor Finally arrive Application Interceptor . 

# How does OkHttp cache data?

# Cache policy

OkHttp Used CacheInterceptor The interceptor controls the data cache using CacheStrategy The above flowchart is implemented, which is based on the previously cached results and the current to be sent Request of header Make a policy and get the result of whether to make the request. According to the output networkRequest and cacheResponse Is the value of null Different strategies are given as follows:

networkRequest cacheResponse result result null null only-if-cached (Indicates that no network request is made, and the cache does not exist or has expired. 503 error will be returned) null non-null No network request, directly return to the cache, no network request non-null null A network request is required, and the cache does not exist or used to access the network directly non-null non-null Header Contains ETag/Last-Modified Tag, do you need to request when the conditions are met, or do you need to access the network

# Cache algorithm

Through analysis CacheInterceptor Interceptor intercept Method, we can find that the specific cache is used Cache Class, and finally the specific implementation is in DiskLruCache Class. Cache is actually a complex logic. It is a separate function block and does not actually belong to OKhttp The function on is actually through http Agreement and DiskLruCache Did some processing. LinkedHashMap Can achieve LRU Algorithm, and in this case In, it is used as a pair DiskCache Memory index for

# What is the connection pool reuse mechanism of OkHttp?

# link 

RealConnection yes Connection The implementation class of represents the link socket If you have a link RealConnection This means that we have a communication link with the server, and RealConnection Represents a connection socket Link, RealConnection Object means that we have a communication link with the server. in addition StreamAllocation Class is a flow bridge, in RetryAndFollowUpInterceptor Initialization in ConnectInterceptor In progress newStream Operation, the 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);

}

newStream Creating a reservation will be called to findConnection Method, which is the key to connection reuse. If<typo id="typo-10928" data-origin="again" ignoretag="true">again</typo>If a reusable connection is found in the connection pool, it is returned directly. Otherwise it will RealConnection Join to link pool ConnectionPool in,The specific codes are 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<Route> 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;

}

# Connection pool

OkHttp Used in ConnectionPool Administration http and http/2 To reduce network request latency. Same address Will share the same connection. This class achieves the goal of reusing connections. One OkHttpClient Contains only one ConnectionPool,In fact, instantiation is also in OkHttpClient The process of. Let's talk about it here ConnectionPool The call of each method is not directly exposed to the outside world, but through OkHttpClient of Internal The interface is uniformly exposed to the outside world.

1,Get connection usage get method,Or get whether there is an appropriate link, otherwise return null,The specific implementation is 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);
return connection;
}
}
return null;
}

2,Join connection usage put Method, and will trigger cleanupRunnable,Clean up the connection. The specific implementation is as follows:

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

3,For the specific connection recycling mechanism, first count the number of idle connections, and then for Cycle to find the connection with the longest idle time and the corresponding idle time, and then judge whether the maximum number of idle connections is exceeded(maxIdleConnections)Or the maximum idle time is exceeded(keepAliveDurationNs),If one of them is satisfied, the connection with the longest idle time will be cleared. If the cleaning conditions are not met, a corresponding waiting 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;
  }

Android core knowledge points

In fact, the success of the interview is inevitable, because I have done enough preparatory work, including brushing questions, reading some core knowledge points of Android, reading some interview blogs and absorbing some experience of everyone's interview.

The following PDF is a blog that I have been browsing for about 3 months or so by some Android bloggers. From them, I picked the essence of some Android's core knowledge points, which are all the essence of the essence. I can interview the 2-2 senior developers now and I have a close relationship with the Android core knowledge that I have compiled. Here in a win-win attitude to share with all friends.

Whether it's Android foundation, Java foundation and common data structures, these are unprincipled and must be mastered. Especially for students who are not computer majors, the interviewer must ask you about your foundation. If the foundation performance is not good, it's easy to be labeled as not solid. Those are the common ones, As long as you usually think about it carefully, there is basically no big problem in the interview.

Finally, in order to help you deeply understand the principle of Android related knowledge points and interview related knowledge, here is the analysis of 2019-2021BAT interview real questions I collected and sorted out. I sorted the technical points often asked in large factory interviews into PDF, including knowledge context + many details.

You can save the time of searching information on the Internet to learn, and you can also share it with your friends to learn together.

Up to now, 2-2 senior developers have an inseparable relationship with the core knowledge points of Android I sorted out. Here, they share it with all friends in a win-win attitude.

[external chain picture transferring... (img-nNG0swaJ-1630507831927)]

Whether it's Android foundation, Java foundation and common data structures, these are unprincipled and must be mastered. Especially for students who are not computer majors, the interviewer must ask you about your foundation. If the foundation performance is not good, it's easy to be labeled as not solid. Those are the common ones, As long as you usually think about it carefully, there is basically no big problem in the interview.

Finally, in order to help you deeply understand the principle of Android related knowledge points and interview related knowledge, here is the analysis of 2019-2021BAT interview real questions I collected and sorted out. I sorted the technical points often asked in large factory interviews into PDF, including knowledge context + many details.

You can save the time of searching information on the Internet to learn, and you can also share it with your friends to learn together.

CodeChina open source project: Android learning notes summary + mobile architecture Video + big factory interview real questions + project actual combat source code

Posted by rfigley on Wed, 01 Sep 2021 18:07:57 -0700