okhttp source code analysis (1): overall analysis

foreword

What are the most popular web frameworks today? That must be okhttp, it has been open source for a long time, and it has always been the favorite of developers. For example, the most popular one: Rxjava + Retrofit + okhttp, all use okhttp as the basis for network layer implementation, image loading The framework Fresco also supports setting okhttp as the network layer. Why is everyone giving the green light to okhttp? No doubt, it must be the strong technical strength of okhttp. Now let's take a look at the source code of okhttp.

text

Let's review the basic usage of okhttp first:

public class MainActivity extends AppCompatActivity {

    public static final MediaType JSON = MediaType.parse("application/text; charset=utf-8");

    private OkHttpClient okHttpClient;

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
        okHttpClient = new OkHttpClient.Builder().build();
        findViewById(R.id.get_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                get();
            }
        });
        findViewById(R.id.post_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                post();
            }
        });
    }

    private void get() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                Request request = new Request.Builder()
                        .url("http://www.baidu.com")
                        .build();
                try {
                    // execute is a synchronous method and will not start a new thread, so Thread is manually created here
                    final Response response = okHttpClient.newCall(request).execute();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                textView.setText(response.body().string());
                            } catch (IOException e) {
                                e.printStackTrace ();
                            }
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace ();
                }
            }
        }.start();

    }

    private void post() {
        RequestBody body = RequestBody.create(JSON, "test");
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .post(body)
                .build();

        okHttpClient.newCall(request)
                // enqueue is an asynchronous method, running in the thread pool
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(@NonNull Call call, @NonNull IOException e) {

                    }

                    @Override
                    public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
                        final String result = response.body().string();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                textView.setText(result);
                            }
                        });
                    }
                });
    }
}

The code implements the get request and the post request. There is no difference between the two requests, but the post method is called more often, the method of the request is clarified, and then the Call object is generated through the request object to execute the network request: execute is a synchronous method and will not be created. A new thread, so we need to manually create a thread, enqueue is an asynchronous method, it will run in the thread pool inside okhttp, and the result of the network is in the response.

Today's content is to analyze the process from creating Request to getting Response.

First let's look at the Request object:

public final class Request {
  final HttpUrl url; // url address information
  final String method; // methods get, post, put, etc.
  final Headers headers; // header信息
  final @Nullable RequestBody body; // Request body, including parameter information, etc.
  final Object tag; // The tag of this request, you can cancel the network request according to the tag
  // Synchronize
  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }
  // setter and getter
  public HttpUrl url() {
    return url;
  }
  
  // Equivalent to the clone method, copy all the configuration of the current Request and return a new Request
  public Builder newBuilder() {
    return new Builder(this);
  }

  /**
   * Returns the cache control directives for this response. This is never null, even if this
   * response contains no {@code Cache-Control} header.
   */
  public CacheControl cacheControl() {
    CacheControl result = cacheControl;
    return result != null ? result : (cacheControl = CacheControl.parse(headers));
  }

  public boolean isHttps() {
    return url.isHttps();
  }

  public static class Builder {
   ...

    public Builder() {
      // default is get method
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

    // setter and getter
}

The Request is mainly about some information of the recorded request, there is no complicated logic, and then the call is made

final Response response = okHttpClient.newCall(request).execute();

First look at the newCall method:

@Override public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}

Internally calls RealCall.newRealCall and enters the RealCall class:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // 重试的Interceptor
  }

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

Seeing this, I understand that okhttpClient.newCall creates a RealCall object, which contains OkhttpClient and Request information. The key is the execute method:

@Override public Response execute() throws IOException {
    synchronized (this) { // If the task has already started, it will not execute and throw an exception
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this); // eventlistener of okhttpclient, set in builde, not introduced here
    ...
      client.dispatcher().executed(this); // Save RealCall to the list of executing tasks
      // This is the most critical code. Through the chain processing of Interceptors, the Response is finally obtained and returned
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
   ...
  }
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors()); // This is the interceptors we set ourselves in okhttpclient, because the priority is the highest, so we can completely customize the returned Response
    interceptors.add(retryAndFollowUpInterceptor); // Interceptor for automatic retry, you have just seen the created code
    interceptors.add(new BridgeInterceptor(client.cookieJar())); // The bridged Interceptor mainly processes the information in the header
    interceptors.add(new CacheInterceptor(client.internalCache())); // Cache the Interceptor to determine whether to use the cache
    interceptors.add(new ConnectInterceptor(client)); // Network connection Interceptor, read network data
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors()); // Here are the networkInterceptors set by okhttpclient
    }
    interceptors.add(new CallServerInterceptor(forWebSocket)); // Read network data Interceptor

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

    return chain.proceed(originalRequest);
  }

Here, the Interceptors we set and the built-in Interceptors are combined and encapsulated into RealInterceptorChain objects. Here, the priorities of various Interceptors are clarified, which is very important for how to set up Interceptors. The end point of our analysis is in the proceed method:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError(); // Determine whether the recursion has crossed the bounds

    calls++;

    // some validation, omitted here

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout); // Create a Chain and get various configuration information of the previous Chain
    Interceptor interceptor = interceptors.get(index); // Get the interceptor of the current index
    Response response = interceptor.intercept(next); // The current Interceptor executes the next chain

    // some validation, omitted here
    return response;
  }

This should be the most difficult place to understand. We didn't see the for loop here, so how do Interceptors respond in order? The key lies in how we implement the Interceptor interface:

new Interceptor() {
     @Override
     public Response intercept(Chain chain) throws IOException {
          return null;
     }
}

In the current Interceptor, call and execute the first Chain.proceed() method;

The second Interceptor is executed in the first Chain.proceed();

The second Interceptor executes the second Chain.proceed();

And the third Interceptor is executed in the second Chain.proceed();

....

This forms recursion. We know that the Interceptor set in okhttpclient has the highest priority. What if we return null directly? Because we interrupted the recursion, null is returned directly, and the subsequent Interceptors will not be executed, so be very careful about defining Interceptors.

After reading execute, look at the enqueue method:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback)); // here is the point, see what is done in dispatcher
  }
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {// Whether the request book should reach the maximum
      runningAsyncCalls.add(call); // put into the queue of running tasks
      executorService().execute(call); // The thread pool runs call, then this call must implement Runnable
    } else {
      readyAsyncCalls.add(call); // Load into the queue waiting to run
    }
  }

Take a look at AynCall's code:

final class AsyncCall extends NamedRunnable { // NamedRunnable inherits Runnable and names each Runnable
    private final Callback responseCallback; // This is the callback parameter passed in the enqueue

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(); // Familiar code, same as in execute
        if (retryAndFollowUpInterceptor.isCanceled()) { // Whether the task was canceled
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled")); // canceled, callback onFailed
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response); // Return Response result
        }
      } 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); // eventlistener set by okhttpclient
          responseCallback.onFailure(RealCall.this, e); // 回调onFailed
        }
      } finally {
        client.dispatcher().finished(this); // finish the task from the queue
      }
    }
  }

Reading data from the server is in CallServerInterceptor. Although it is only a simple read and write operation, okhttp is very carefully encapsulated, so it is omitted here for the time being. Later, we will carefully analyze the reading of Response as a very important part.

Summarize

Do you think it's easy? Now we just figure out the main line operation of okhttp, other functions such as cache, thread pool, etc. need to study the specific details. As the beginning of studying the okhttp source code, it has laid a very solid foundation for our subsequent research.

Finally, have a nice weekend everyone! ! !


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324659800&siteId=291194637