1 a brief introduction

OKhttp3 is an efficient Http client that supports the connection to the same address to share the same socket, reducing response latency through connection pooling, transparent GZIP compression, request caching, and other advantages. The core of its main routing, connection protocol, interceptor, proxy, security authentication, connection pooling, and network adaptation. Removes or transforms headers for requests or responses.

Let’s look at OkHttp3 again with this simple flow chart.

Note: This analysis is based on"Com. Squareup. Okhttp3: okhttp: 3.14.9"Version.

2 Basic Usage

Here is an example of a GET request using OkHttp.

OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
    .url("https://api.github.com/")
    .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {}@Override
    public void onResponse(Call call, Response response) throws IOException { String result = response.body().string(); }});Copy the code

From this example, it is not hard to see the official implementation of a design aesthetic in it. OkHttpClient = new OkHttpClient(); Let’s think of it like getting a mailbox. Next, we write a letter, new request.builder ().url(“https://api.github.com/”).build(); We can add a lot of content to a letter, and the most important mailing address is URL. Step 3, we put the letter into the mailbox, okHttpClient.newCall(request); Step 4, wait for the result to return. One of the nice things about OkHttp is that the developer doesn’t have to worry about, how does the request go out? How are massive requests maintained? What happens to the returned results? Just, “Write the letter” and put it there, and wait for the (asynchronous \ synchronous) result to return.

3 Principle Analysis

Let’s ask three questions to see what OkHttp core logic does.

  • Where did the request go?
  • How are mass requests maintained?
  • How will the request be processed? How are returns received?

3.1 Where does the request go? How are mass requests maintained?

Following the source code from call.enqueue(new Callback(), you’ll find a Dispatcher class.

There are several queues in this class:

/** Queue of asynchronous requests */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous request queue */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** A running synchronous request queue (which contains requests that have been canceled, but have not yet finished) */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Copy the code

If you look at the code, you’ll see that all requests flow through three queues. Take asynchrony for example:

void enqueue(AsyncCall call) {
    synchronized (this) {
        readyAsyncCalls.add(call); // Requests are queued in sequence

        // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
        // the same host.
        if(! call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host());if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall);
        }
    }
    promoteAndExecute();
}
Copy the code

PromoteAndExecute () method

private boolean promoteAndExecute(a) {
    assert(! Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        // Get the queue of waiting tasks
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
			// The maximum number of requests that can be run at the same time exceeds 64
            if (runningAsyncCalls.size() >= maxRequests) break;
            // The number of tasks that can be concurrently run on a host exceeds 5
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;

            // The number of running queue tasks is less than 64 and the number of simultaneous running requests from the same host is less than 5.
            // Add it to the run-time queue.
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        // Execute each asynchronous request
        asyncCall.executeOn(executorService());
    }

    return isRunning;
}
Copy the code

ReadyAsyncCalls, then runningAsyncCalls, and finally call the thread pool to execute each asynchronous request!

// Execute the source code
void executeOn(ExecutorService executorService) {
    assert(! Thread.holdsLock(client.dispatcher()));boolean success = false;
    try {
        // Put it into the thread pool
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        if(! success) { client.dispatcher().finished(this); // This call is no longer running!}}}// call in NamedRunnable
@Override protected void execute(a) {
    boolean signalledCallback = false;
    transmitter.timeoutEnter();
    try {
        Response response = getResponseWithInterceptorChain();
        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 {
            responseCallback.onFailure(RealCall.this, e); }}catch (Throwable t) {
        cancel();
        if(! signalledCallback) { IOException canceledException =new IOException("canceled due to " + t);
            canceledException.addSuppressed(t);
            responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
    } finally {
        client.dispatcher().finished(this); }}Copy the code

Look at the specific thread executor created by OkHttp!

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

Parameter Description ThreadPoolExecutor

  • Int corePoolSize: number of core threads. If 0, all threads will be destroyed after a period of idle time.
  • Int maximumPoolSize: Specifies the maximum number of threads that can be expanded when a task comes in. If the number is greater than this, it will be discarded.
  • Long keepAliveTime: Maximum keepAliveTime for extra idle threads when the number of threads is greater than corePoolSize, similar to keep-alive in HTTP.
  • TimeUnit Unit: indicates the unit of time, usually in seconds.
  • BlockingQueue workQueue: workQueue, first in, first out.
  • ThreadFactory ThreadFactory: A factory for a single thread that can Log and set up daemons (that is, threads end automatically when the JVM exits).

To recap: So far, let’s take the asynchronous request as an example. Where does the request go? How are requests maintained in queues? “, then the synchronous request can be immediately executed without placing it in the runningSyncCalls queue.

3.2 How are Requests processed? How are returns received?

Let’s take a look at how the request is processed and sent to the server step by step, which forces us to analyze a wave of OkHttp’s classic interceptor responsibility chain.

Response getResponseWithInterceptorChain(a) throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, 
                                                       transmitter, null.0,
                                                       originalRequest, this, 
                                                       client.connectTimeoutMillis(),
                                                       client.readTimeoutMillis(), 
                                                       client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
        Response response = chain.proceed(originalRequest);
        if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
        }
        return response;
    } catch (IOException e) {
        calledNoMoreExchanges = true;
        throw transmitter.noMoreExchanges(e);
    } finally {
        if(! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code
  • Client.interceptors () : user-defined Interceptor.
  • Retry and redirect RetryAndFollowUpInterceptor: responsible for the failure.
  • BridgeInterceptor: Is responsible for converting user-constructed requests into requests sent to the server, and for converting responses returned from the server into user-friendly responses.
  • CacheInterceptor: Reads the cache and updates the cache.
  • ConnectInterceptor: Establishes a connection with the server.
  • Client.net workInterceptors () : a user-defined Interceptor network layer
  • CallServerInterceptor: Is responsible for reading the response data from the server.

From the source code, we can roughly see that a request is processed by several interceptors, issued in the CallServerInterceptor interceptor phase, and returned, and then from the bottom of the interceptor, wrapped up and returned. So, we can see that each interceptor will return response up.

@Override public Response intercept(Chain chain) throws IOException {
    // Request phase processingRequest request = chain.request(); ./ / returns the response
    return response;
}
Copy the code

How does OkHttp3 process requests in several queues? How do you layer requests? How to encapsulate the return? More details on what each interceptor does will be updated later.