Introduction to Okhttp synchronous and asynchronous request knowledge

1. Basic concepts

1.1. Synchronous
Synchronous request means that the program will be in a blocked state until the response result is received, and cannot receive new requests until the response is received
1.2. Asynchronous
asynchronous request means that the request is initiated and before the response result is obtained , The program is in a non-blocking state and can continue to receive new requests. When the response comes back, a callback will be called to process the response data.
1.3 Blocking
Blocking means that the current thread will be suspended before the request result is returned. Blocking and synchronization are essentially not equivalent. For synchronization, many times the current thread is still active and can continue to process various other messages, but the request cannot be returned.
1.4. Non-
blocking Non -blocking is the opposite of blocking. Before the result can not be obtained immediately, the current thread will not be blocked, and it will return immediately

Finally, blocking and non-blocking objects are not an absolute concept, non-blocking processing may also be incorporated into blocking; blocking processing may also be incorporated into non-blocking

Two, Okhttp synchronization request

2.1 Advantages and disadvantages

The advantages of synchronous requests are : simple understanding; simple implementation; the response result can be returned for subsequent processing;

The disadvantage of synchronous request is that it cannot support high concurrency, and it will affect performance in scenarios where real-time request response is not high.

2.2 Implementation

public static String postForPBSerializeForm(String url, JSONObject requestParam){
    
    
    System.out.println("requestParam: " + requestParam.toJSONString());
    try {
    
    
        OkHttpClient client = new OkHttpClient();

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        formBodyBuilder.add("id", requestParam.getString("id"));
        formBodyBuilder.add("version", requestParam.getString("version"));
        formBodyBuilder.add("mockData", requestParam.getString("mockData"));
        formBodyBuilder.add("type", requestParam.getString("type"));

        Request request = new Request.Builder()
                .post(formBodyBuilder.build())
                .url(url)
                .build();
        //okhttp 同步请求关键代码
        Response response = client.newCall(request).execute();
        System.out.println("response.code():" + response.code() + "         " + (response.code() == 200));
        if (response.code() == 200) {
    
    
            return response.body().string();
        }
    }catch (Exception e){
    
    
        System.out.println("'sdew");
        e.printStackTrace();
    }
    return null;
}

Three, Okhttp asynchronous request

3.1 Advantages and Disadvantages The advantage of
asynchronous requests is: support for highly concurrent interface requests

The disadvantages of asynchronous requests are : complex implementation; response results can only be output, not returned, which is not conducive to the subsequent processing of the interface

3.2 Implementation

public static String postForPBSerializeFormAsync(String url, JSONObject requestParam){
    
    
    System.out.println("requestParam: " + requestParam.toJSONString());
    try {
    
    
        OkHttpClient client = new OkHttpClient();

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        formBodyBuilder.add("id", requestParam.getString("id"));
        formBodyBuilder.add("version", requestParam.getString("version"));
        formBodyBuilder.add("mockData", requestParam.getString("mockData"));
        formBodyBuilder.add("type", requestParam.getString("type"));

        Request request = new Request.Builder()
                .post(formBodyBuilder.build())
                .url(url)
                .build();
        //okhttp异步请求的关键代码
        Call call=client.newCall(request);
        call.enqueue(new Callback() {
    
    
            @Override
            public void onFailure(Call call, IOException e) {
    
    
                System.out.println("shibai");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
    
    
                System.out.println("success:" + response.body().string());
            }
        });
    }catch (Exception e){
    
    
        System.out.println("'sdew");
        e.printStackTrace();
    }
    return null;
}

3.3 Specific execution flow chart

3.4 Introduction to related knowledge points

3.4.1 Call

Call is an interface, A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), it cannot be executed twice. It is given in the source code The role and characteristics. That is: (1) a request to be executed; (2) can be cancelled; (3) a call can only be responsible for executing a request

Because OkHttpClient implements Call.Factory, it has the function of creating Call objects;
OkHttpClient part of the source code:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    
    
  /**
   * Prepares the {
    
    @code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    
    
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
}

3.4.2 RealCall (implementation class of Call)

RealCall truly implements methods such as synchronous and asynchronous requests and cancellation requests defined within the Call interface class

Key attributes of RealCall

  • OkHttpClient client: It is the OkHttpClient object we created, through which the Dispatcher is called to distribute request tasks;
  • boolean executed: A Call can only be executed once, which is controlled by this attribute. When it is true, the request has been executed.
  • Request originalRequest: The originalRequest object is the Request object passed in through OkHttpClient.newCall(request). This object plays a very important role in the entire network request. It will be passed to each Interceptor. For example, the Request object created by the user simply sets url, method, requestBody and other parameters, but if you want to send a network request, this simple configuration is not enough. The interceptor BridgeInterceptor provided by the system is responsible for doing this. It will add request headers to the request, such as gzip, cookie, content-length, etc. Simply put, it will add some parameters to the request created by the user to make it more consistent with the request to the network. The function of other interceptors is also to operate on the request

3.4.3 Dispatcher (asynchronous task dispatcher)

Dispatcher is only used for asynchronous requests. It internally assigns a thread pool to perform asynchronous tasks, and provides a finish method to end the asynchronous request after the execution is completed. Then get the next asynchronous task that meets the conditions from the waiting queue to execute.

Source code:

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import okhttp3.RealCall.AsyncCall;
import okhttp3.internal.Util;

/**
 * Policy on when async requests are executed.
 *  * <p>Each dispatcher uses an {
    
    @link ExecutorService} to run calls internally. If you supply your
 * own executor, it should be able to run {
    
    @linkplain #getMaxRequests the configured maximum} number
 * of calls concurrently.
 */
public final class Dispatcher {
    
    
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> 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<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

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

  /**
 * Set the maximum number of requests to execute concurrently. Above this requests queue in
 * memory, waiting for the running calls to complete.
 *  * <p>If more than {@code maxRequests} requests are in flight when this is invoked, those requests
 * will remain in flight.
   */
  public void setMaxRequests(int maxRequests) {
    if (maxRequests < 1) {
      throw new IllegalArgumentException("max < 1: " + maxRequests);
    }
    synchronized (this) {
      this.maxRequests = maxRequests;
    }
    promoteAndExecute();
  }

  public synchronized int getMaxRequests() {
    return maxRequests;
  }

  /**
 * Set the maximum number of requests for each host to execute concurrently. This limits requests
 * by the URL's host name. Note that concurrent requests to a single IP address may still exceed
 * this limit: multiple hostnames may share an IP address or be routed through the same HTTP
 * proxy.
 *  * <p>If more than {
    
    @code maxRequestsPerHost} requests are in flight when this is invoked, those
 * requests will remain in flight.
 *  * <p>WebSocket connections to hosts <b>do not</b> count against this limit.
   */
  public void setMaxRequestsPerHost(int maxRequestsPerHost) {
    
    
    if (maxRequestsPerHost < 1) {
    
    
      throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
    }
    synchronized (this) {
    
    
      this.maxRequestsPerHost = maxRequestsPerHost;
    }
    promoteAndExecute();
  }

  public synchronized int getMaxRequestsPerHost() {
    
    
    return maxRequestsPerHost;
  }

  /**
 * Set a callback to be invoked each time the dispatcher becomes idle (when the number of running
 * calls returns to zero).
 *  * <p>Note: The time at which a {
    
    @linkplain Call call} is considered idle is different depending
 * on whether it was run {
    
    @linkplain Call#enqueue(Callback) asynchronously} or
 * {
    
    @linkplain Call#execute() synchronously}. Asynchronous calls become idle after the
 * {
    
    @link Callback#onResponse onResponse} or {@link Callback#onFailure onFailure} callback has
 * returned. Synchronous calls become idle once {
    
    @link Call#execute() execute()} returns. This
 * means that if you are doing synchronous calls the network layer will not truly be idle until
 * every returned {
    
    @link Response} has been closed.
   */
  public synchronized void setIdleCallback(@Nullable Runnable idleCallback) {
    
    
    this.idleCallback = idleCallback;
  }

  void enqueue(AsyncCall call) {
    
    
    synchronized (this) {
    
    
      readyAsyncCalls.add(call);

      // 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();
  }

  @Nullable private AsyncCall findExistingCallWithHost(String host) {
    
    
    for (AsyncCall existingCall : runningAsyncCalls) {
    
    
      if (existingCall.host().equals(host)) return existingCall;
    }
    for (AsyncCall existingCall : readyAsyncCalls) {
    
    
      if (existingCall.host().equals(host)) return existingCall;
    }
    return null;
  }

  /**
 * Cancel all calls currently enqueued or executing. Includes calls executed both {
    
    @linkplain
 * Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}.
   */
  public synchronized void cancelAll() {
    
    
    for (AsyncCall call : readyAsyncCalls) {
    
    
      call.get().cancel();
    }

    for (AsyncCall call : runningAsyncCalls) {
    
    
      call.get().cancel();
    }

    for (RealCall call : runningSyncCalls) {
    
    
      call.cancel();
    }
  }

  /**
 * Promotes eligible calls from {
    
    @link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
 * them on the executor service. Must not be called with synchronization because executing calls
 * can call into user code.
 *  * @return true if the dispatcher is currently running calls.
   */
  private boolean promoteAndExecute() {
    
    
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
    
    
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    
    
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        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);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

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

  /** Used by {
    
    @code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    
    
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
  }

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

  private <T> void finished(Deque<T> calls, T call) {
    
    
    Runnable idleCallback;
    synchronized (this) {
    
    
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
    
    
      idleCallback.run();
    }
  }

  /** Returns a snapshot of the calls currently awaiting execution. */
  public synchronized List<Call> queuedCalls() {
    
    
    List<Call> result = new ArrayList<>();
    for (AsyncCall asyncCall : readyAsyncCalls) {
    
    
      result.add(asyncCall.get());
    }
    return Collections.unmodifiableList(result);
  }

  /** Returns a snapshot of the calls currently being executed. */
  public synchronized List<Call> runningCalls() {
    
    
    List<Call> result = new ArrayList<>();
    result.addAll(runningSyncCalls);
    for (AsyncCall asyncCall : runningAsyncCalls) {
    
    
      result.add(asyncCall.get());
    }
    return Collections.unmodifiableList(result);
  }

  public synchronized int queuedCallsCount() {
    
    
    return readyAsyncCalls.size();
  }

  public synchronized int runningCallsCount() {
    
    
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }
}

The key attributes of Dispatcher, these attributes will affect the execution of asynchronous requests

  • int maxRequests = 64: Specify the maximum number of concurrent requests
  • int maxRequestsPerHost = 5: The maximum number of requests per host is 5, that is, up to 5 calls share one host. This host is obtained through originalRequest.url().host() in RealCall, such as www.baidu.com
  • ExecutorService executorService: The thread pool that executes asynchronous tasks. The thread pool has been specified internally. Of course, you can also specify a thread pool through the construction method in Dispacther
  • Deque readyAsyncCalls: indicates that the request has been prepared in the queue
  • Deque runningAsyncCalls: Represents the asynchronous requests being executed in the queue, including requests that have been cancelled (requests that have not yet performed the finish operation)
  • Deque runningSyncCalls: running synchronization requests , including requests that have been cancelled (requests that have not yet performed the finish operation)

3.4.4 AsyncCall

The AsyncCall class is an internal class defined in the RealCall class, which represents an asynchronous request. The asynchronous request task distributed in the Dispatcher is AsyncCall, and the Dispatcher will specify a thread from the thread pool to execute the AsyncCall task. Two queues are defined inside Dispatcher to store AsyncCalls, namely readyAsyncCalls and
runningAsyncCalls. They respectively represent the AsyncCall queue to be executed and the AsycnCall queue being executed; of course there is also a runningSyncCalls queue, but it is suitable for storing RealCall, that is , the task for storing synchronization requests .

Fourth, solve the problem of not being able to return the response result of asynchronous request

Reference blog:
Simple analysis of Okhttp's synchronous and asynchronous requests OKHTTP simple analysis of
asynchronous and synchronous requests

Guess you like

Origin blog.csdn.net/sinat_34241861/article/details/114307539