Preface:

This article also made a title party, ha ha, in fact, still very water, you forgive me O(∩_∩).

Between their own network knowledge rotten in a complete mess, so ready to write the relevant network article, but consider all written in one is too long, so separate write, I hope you can look carefully, the best can point out my mistakes, let me also can correct.

1. Explain the whole network architecture:

Android Skill Tree – Network Summary (1) network Architecture

TCP, UDP,socket,websocket, HTTP, HTTPS. What is webService? It is similar to websocket. Socket and Websocket are similar in terms of session,token and cookie.

Android Skill Tree – Network Summary (2) TCP/UDP

Android Skill Tree – Web Summary (3) HTTP/HTTPS

Android skill tree – network summary (4) of the socket/websocket/webservice

Cookie /session/token (to be written)

3. Source code analysis of relevant third party frameworks, after all, okHTTP and Retrofit source code is a must for interviewing large companies now.

Android Skill Tree – Network summary (6) OkHttp super super super super super super super super super super detail analysis

Android Skill Tree – Network Summary (7) Retrofit source code detailed parsing


Okhttp3.xxx is the latest version of okhttp3.xxx. This is the latest version of okhttp3.xxx. I’ve been asked where to view the latest version of the XXX third-party library before, so I thought I’d mention this. It’s actually quite simple, let’s take okhttp as an example:

  1. Android Studio
  2. JCenter:Search for the Okhttp version on JCenter
  3. Maven:Search for the Okhttp version on Maven
  4. . The other way

The body of the

If you can’t see clearly, you can right-click, choose New TAB to open, and then click on the image to enlarge

First, let’s determine the overall outline:

  1. Okhttp related parameter configuration, such as setting timeout, network path, etc……..
  2. We know that we can use both synchronous and asynchronous requests with OKHTTP, so different requests must be handled differently when distributed.
  3. Network series of articles mentioned before, we sent to the background, it must be a complete request packet, but when we use okhttp, just turn to the parameters that we need to give the background, even if we are get request, is introduced into the corresponding url network address can get the data, explain okhttp help us put the simple input parameters, Then, through a series of add-encapsulation, it becomes a complete network request packet, and then when we use OKHTTP, we get the returned data is already the object that we can use directly, so when we accept it, we have already parsed the returned network packet into the object that we can use directly. So the process of adding parameters in a series of requests for us to send into a complete network request packet, and helping us to parse the return request packet when received is handled by Okhttp’s interceptors, which intercept our data and then process it, such as adding some data into a complete network request packet and so on.

So that gives us an idea of what okHTTP is all about.

1. Basic use of OKHTTP:

Before explaining source code, first write okhttp basic use, so it is more convenient to explain source code:

String url = "http://www.baidu.com";
//'1. Generate OkHttpClient instance object '
OkHttpClient okHttpClient = new OkHttpClient();
//'2. Generate the Request object '
Request request = new Request.Builder().url(url).build();
//'3. Generate Call object '
Call call = okHttpClient.newCall(request);
//'4. If you want to execute a synchronization request: 'try { call.execute(); } catch (IOException e) { e.printStackTrace(); } / /'5. If you want to execute an asynchronous request: '
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
    }
});
Copy the code

2. Initialization related parameter parsing:

Let’s take a look at our initial complete flowchart:

The OkhttpClient, Request, and Call objects need to be prepared before performing synchronization and asynchrony. Let’s look at the relevant source code step by step:

2.1 OkHttpClient related:

Our code above instantiates the OkHttpClient object:

OkHttpClient okHttpClient = new OkHttpClient();
Copy the code

Let’s take a look:

OkHttpClient
Builder
new OkHttpClient()
Builder
Builder

We can see the last few values:

. connectTimeout = 10_000;readTimeout = 10_000; writeTimeout = 10_000; .Copy the code

The default connection timeout, read timeout, write timeout, is 10 seconds, and then there are other default properties, so we want to change those properties, like the timeout to 20 seconds, very easy. Instead of using the default Builder object:

OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.connectTimeout(20,TimeUnit.SECONDS); builder.readTimeout(20,TimeUnit.SECONDS); builder.writeTimeout(20,TimeUnit.SECONDS); OkHttpClient okHttpClient = builder.build(); OkHttpClient = new OkHttpClient(Builder); // This is the wrong builder.build(); Public OkHttpClientbuild() {
    return new OkHttpClient(this);
}
Copy the code

Let’s go back to the OkHttpClient and see what the values of the properties are:

  • Dispatch: the dispatcher, which will be mentioned later

  • Proxy: Sets the Proxy, usually type (HTTP, SOCKS) and socket address. Reference article: Create a connection directly using Proxy

  • ProxySelector: Sets the global proxy, inheriting the class to set the type, address, and port of the specific proxy. See article: Java proxies set up global proxies with ProxySelector

  • Protocol: Network protocols, such as HTTP1.0, HTTP1.1, and HTTP2.0.

  • ConnectionSpec: Specifies the configuration of the socket connection through which HTTP traffic passes. We can directly translate the English introduction of this kind of head, forgive me for not quite understanding the specific content:

  • Interceptor: Interceptor, which we’ll talk about later

  • EventListener: Listener for indicator events. Extend this class to monitor the number, size, and duration of HTTP calls to your application. All start/ Connect/Acquire events will eventually receive a matching end/Release event, either successful (non-NULL arguments) or unsuccessful (non-NULL throwable). The first public parameter of each event pair is used to link events in case of concurrent or repeated events, such as dnsStart(call, domainName); And dnsEnd(call, domainName, inetAddressList); We can see a series of xxxStart and xxxEnd methods:

  • CookieJar: Adds cookies to outgoing HTTP requests and cookie processing to received HTTP return data.

    Reference article:Okhttp3 with cookie request

  • Cache: The network Cache. By default, OKHTTP can only Cache GET requests, but not POST requests. POST requests are mostly interactive, so caching them is meaningless.

    When we look at the constructor of the Cache, we can see that we need to set the Cache folder, the size of the Cache, and the internal operation of the Cache, because the Cache needs to write files, the default operation is Okio.

    Reference article:

    How to use okHTTP cache

    OKHTTP cache configuration details

  • InternalCache: Okhttp InternalCache interface, we do not need to implement this interface, but directly use the above Cache class.

  • SocketFactory: The Android Socket factory class. Reference article: Class SocketFactory

  • SSLSocketFactory: The SSLSocket factory class that comes with Android. Use SSLSocket to connect to HTTPS using SSLSocketFactory

  • CertificateChainCleaner: Not very well, so it is still the same, through Google Translation, translation of the top note:

  • HostnameVerifier: literal, Hostname authentication, a basic interface with only one method:

** * Verify that the hostname is an acceptable match with * the server's authentication scheme. ** @param hostname the host name * @param session SSLSession used on the connection to host * @return true if the host name is acceptable
 */
public boolean verify(String hostname, SSLSession session);
Copy the code
  • Dns: The Domain Name System (Dns) is used to resolve Domain names into IP addresses. Reference article: Android DNS Update with DNS-Prefetch
  • And so on……

2.2 Request related

Let’s look at the Request code:

public final class Request { final HttpUrl url; // Network request path final String method; / / the get and post... final Headers headers; // Final @nullable RequestBody body; // Request body /** You can cancel multiple requests at the same time with tags. When you build a request, use requestBuilder.tag (tag) to assign a tag. You can then cancel all calls with this tag using okHttpClient.cancel (tag). . */ final Map<Class<? >, Object> tags; . . . }Copy the code

If you’re not sure about the headers and body of the request, check out our previous article in this series: Android Skill Tree — Web Summary (3) HTTP/HTTPS

2.3 Call related

We can see that our generated Request instance is passed to the newCall method of the OkHttpClient instance:

Request request = new Request.Builder().url(url).build(); Call call = okHttpClient.newCall(request); call.execute(); Or call the enqueue (...). ;Copy the code

Now that we’ve seen both Request and OkHttpClient, let’s take a look at what newCall does and what Call does.

@override public Call newCall(Request Request) {return RealCall.newRealCall(this, request, false/ *forweb socket */); Static RealCall newRealCall(OkHttpClient, Request originalRequest, BooleanforWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

Copy the code

As you can see, we finally get the RealCall instance and pass in the OkHttpClient and Request with all of our parameters configured.

Call.execute ()/call.enqueue() are the corresponding methods of RealCall execution. But the current position has been explained in the diagram above, I will paste it again:

Congratulations, the next time someone asks you about Okhttp, you will understand the parameters of the configuration of the code.

3. Request Dispatcher

Let’s continue with our flow chart below:

3.1 Dispatcher synchronization operation

Let’s start with synchronous execution:

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //'1. Executed method of Dispatcher '
      client.dispatcher().executed(this);
      //'2. Call the getResponseWithInterceptorChain methods'
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //'3. The Dispatcher finished method must be executed. 'client.dispatcher().finished(this); }}Copy the code

We’ll look at the “executed” method in the Dispatcher class:

/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}
Copy the code

Can see our RealCall joined a synchronous runningSyncCalls thread, and then call the middle getResponseWithInterceptorChain method * (the second operation we will be on the back is the interpretation of specific). We added a synchronized thread and must remove it when it runs out, and the finished method in step 3 does the following:

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
   finished(runningSyncCalls, call, false);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
    
      //'We can see in the if statement that we're removing the call object from our queue.'
      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

3.2 Dispatcher asynchronous operation

Let’s look at the enqueue code in RealCall:

@Override public void enqueue(Callback responseCallback) {
    //'1. There is a synchronous lock throw operation '
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //'2. Call enqueue from Dispatcher '
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code

Let’s take a step-by-step look at the first synchronous lock throw exception operation. We know that a Call should handle a network request.

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 {}
});
//'Request again from the same Call object'
call.enqueue(new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {}

   @Override
   public void onResponse(Call call, Response response) throws IOException {}
});
Copy the code

The same Call object is requested twice. “Executed” is executed and an exception is thrown.

Let’s look at the second step:

We know that asynchronous requests will definitely mean that many requests are executed in their own threads, so we’ll leave it to you to implement it before we look at the OkHttp source code. How do you implement it? Is the first reaction to use thread pools?

The Structure of the Java/Android thread pool framework consists of three parts 1. Task: Includes the interface class to be implemented by the task: Runnable or Callable 2. Task Executor: Includes Executor, the core interface of task execution mechanism, and EexcutorService interface inherited from Executor. 3. Creator of actuators, Factory ExecutorsCopy the code

For details, see The Android ThreadPool Framework, Executor, and ThreadPoolExecutor

client.dispatcher().enqueue(new AsyncCall(responseCallback)); Instead of passing RealCall directly in as a synchronous operation, an AsyncCall object is passed in. Yes, according to the thread pool architecture we mentioned above, the task is to use the Runnable or Callable interface, we look at AsyncCall code:

final class AsyncCall extends NamedRunnable { ...... . } public abstract class NamedRunnable implements Runnable { ....... . }Copy the code

As expected, the Runnable interface was used.

client.dispatcher().enqueue(new AsyncCall(responseCallback)); Instead of passing RealCall directly in as a synchronous operation, an AsyncCall object is passed in.

Call enqueue from Dispatcher:

synchronized void enqueue(AsyncCall call) {
    //'1. Check whether the number of asynchronous queues is less than the maximum number of requests.
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //'2. If it does not exceed the maximum value, add the call to the asynchronous request queue '
      runningAsyncCalls.add(call);
      //'3. And run the call task '
      executorService().execute(call);
    } else{ readyAsyncCalls.add(call); }}Copy the code

The Java/Android thread pool framework has three main parts, and we can see that the Runnable object is a task Executor, which is an Executor descendant. ExecutorService () returns an Executor implementation class.

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

Sure enough, a cacheable thread pool is created. The maximum length of the thread pool is unlimited, but if the length of the thread pool exceeds the processing needs, the free thread can be recycled flexibly. If not, the new thread can be recycled.

We know that the thread pool performs the Runnable task, so we just need to look at what our okhttp Runnable does:

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; }... . . @Override protected voidexecute() {
      boolean signalledCallback = false;
      try {
      
        //'1. We can find the last thread pool implementation task is getResponseWithInterceptorChain methods'
        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 {
        //'2. Finally remove async queue from Dispatcher 'client.dispatcher().finished(this); }}}Copy the code

We find that whether it’s asynchronous or synchronous, it’s the same trilogy :1. Join the synchronous (or asynchronous) queue in the Dispatcher, 2. Perform getResponseWithInterceptorChain method, 3. Remove from the synchronous (or asynchronous) queue within the Dispatcher. (only the synchronous operation is run directly getResponseWithInterceptorChain method, and asynchronous is executed Runnable to go through the thread pool getResponseWithInterceptorChain method)

4 Okhttp intercept

We already know in front either asynchronous or synchronous request, go to perform RealCall getResponseWithInterceptorChain operation:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //'1. Create an interceptor List'
    List<Interceptor> interceptors = new ArrayList<>();
    //'2. Add user-created application interceptor '
    interceptors.addAll(client.interceptors());
    //'3. Add retry and redirection interceptor '
    interceptors.add(retryAndFollowUpInterceptor);
    //'4. Add content blocker '
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //'4. Add cache interceptor '
    interceptors.add(new CacheInterceptor(client.internalCache()));
    /'5. Add connection interceptor '
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //'6. Add user-created network interceptors'interceptors.addAll(client.networkInterceptors()); } / /'7. Add request Service interceptor '
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //'8. Wrap these interceptors together in a RealInterceptorChain '
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    //'9. Then proceed to the chain method '
    return chain.proceed(originalRequest);
  }
Copy the code

Instead of looking at the specific interceptor functionality, let’s look at how the interceptor chain works as a whole:

public final class RealInterceptorChain implements Interceptor.Chain {
   
   //'The queue we just set up to put interceptors'
   private final List<Interceptor> interceptors;
   //'Currently executing interceptor sequence number'private final int index; . . . public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {if(index >= interceptors.size()) throw new AssertionError(); . . . //'Instantiate a new RealInterceptorChain object and pass in the same interceptor List with the index value +1'
   RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
       connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
       writeTimeout);
   //'Interceptor Interceptor = interceptors.get(index); // Then execute the interceptor's intercept method and pass in a new RealInterceptorChain object.Response response = interceptor.intercept(next); . . .returnresponse; }}Copy the code

In the proceed method of the RealInterceptorChain class, another RealInterceptorChain class is instantiated. Many people may look around, it doesn’t matter, let’s take an example to briefly say:

I will write it as it is: two interceptors, one for AddAddressInterceptor and one for AddTelephoneInterceptor, and also one for InterceptorChain. All I have to do is pass in a string, and it automatically fills in the address and phone number for me, as each interceptor does.

Interceptor is only responsible for its own business functions, such as filling in the address and phone number, and then calling the Interceptor chain when its own task is finished. It has nothing to do with the Interceptor chain.

Let’s take a look at our interceptor and interceptor chain:

Phone blocker:

Address interceptor:

Interceptor chain:

Activity.java:

Finally, we can see that our result is:

Here’s an extra note: the two major steps in the interceptor can be swapped. I first execute the interceptor chain method, let it perform the next interceptor operation in advance, and then take the corresponding return value to do my interceptor operation. For example, with the same phone blocker, we switched the order of the two:

This will execute the address blocker first, and then process the call blocker logic after receiving the result, so the final output will be:

The interceptor chain is the same as our basic architecture, and each interceptor handles its own logic, making changes to parameters, making requests, etc. So the core becomes what logic OkHttp’s interceptors actually do. (This is one of the two major operations we mentioned in the interceptor, its own processing logic.)


I wanted to write about the role of each individual interceptor step by step, but on reflection, there are so many articles on the code analysis of individual interceptors. And each interceptor is written very simple, in fact, there is no great significance, written carefully, an interceptor can be an article, and this article also focuses on the overall source architecture, so IF I can, I will directly quote other people’s articles.


4.1 RetryAndFollowUpInterceptor

As the name suggests, this interceptor is used for retry and redirection.

You can refer to this article:

OKhttp source code parsing – RetryAndFollowUpInterceptor interceptors

4.2 BridgeInterceptor

The BridgeInterceptor class is provided in this way:

What? Can’t read English, Google Translate walk:

In short, we created a Request object in Okhttp, but this object is not directly used to send network requests. After all, when we first instantiated the Request, we simply put the Url, body, etc., and many parameters are not set. So we have to fill in a lot of parameters, and then we make the network request, and then the parameters that the network returns, and we wrap that into objects that Okhttp can use directly.

In a word: build the Request object information constructed by the client into a real network Request; It then initiates a network request, and finally encapsulates the message returned by the server into a Response object

Reference article:

OkHttp: BridgeInterceptor

4.3 CacheInterceptor

The cache interceptor, in short, uses the cache when you have it.

Reference article:

Okhttp CacheInterceptor simple analysis

4.4 ConnectInterceptor

The connection interceptor, as its name implies, opens the link to the server, officially opening the network request.

Because in previous articles: Android skill tree – network summary (4) of the socket/websocket/webservice mentioned that our request is to access through socket.

So eventually the ConnectInterceptor will also initiate a Socket connection request.

Reference article:

OkHttp: ConnectInterceptor

4.5 CallServerInterceptor

We mentioned this in the Android Skill Tree — Networking Summary (2) in TCP/UDP:

TCP establishes a channel before sending data.

ConnectInterceptor already provides a channel for us, so in the CallServerInterceptor interceptor, our job is to send relevant data.

Reference article:

Okhttp: CallServerInterceptor

4.6 Customizing Interceptors

As you can see in the flowchart, there are two types of custom interceptors, application interceptors and network interceptors, in addition to the built-in interceptors in the OKHttp source code.

Use code:

OkHttpClient = new okHttpClient.Builder ().addInterceptor(appInterceptor .addNetworkInterceptor(networkInterceptor)//Network interceptor.build();Copy the code

We know that the network Request must go through a series of interceptors. We can also write interceptors ourselves and then process the parameters in the interceptor. For example, we make a write parameter change in the interceptor for the Request and then send it to the next interceptor.

And that the position of the two custom interceptors, in front of us method of analysis for intercepting chain getResponseWithInterceptorChain is mentioned, now take out to say it again:

Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor>  interceptors = new ArrayList<>();'Add user's custom APPlication interceptor first'
    interceptors.addAll(client.interceptors());
    
    'And then a bunch of interceptors that come with Okhttp.'
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    
    'Add a user-defined NetWork interceptor before the CallServerInterceptor finally interacts with the server.'
    'Because it doesn't matter if you put it at the end. '
    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

Reference article:

OkHttp basically uses (v) custom interceptors

Conclusion:

OkHttp source code is also written in a hurry, especially after the source analysis of each interceptor lazy, because otherwise it will lead to a long section of a long section of content, directly quoting other big guys’ articles. If anything is wrong, I welcome you to point it out.