Okhttp同步与异步请求知识介绍

一、基本概念

1.1、同步
同步请求是指在请求发起到拿到响应结果之前,程序一直会处于阻塞状态,无法接收新的请求,直至拿到响应
1.2、异步
异步请求是指请求发起后到拿到响应结果之前,程序是非阻塞状态,可以继续接收新的请求,响应回来时会调用一个回调处理响应数据
1.3、阻塞
阻塞是指请求结果返回之前,当前线程会被挂起。阻塞和同步实质上是不可等同的。对于同步来说,很多时候当前线程仍是处于激活状态的,可以继续处理其他各种消息,只是请求拿不到返回而已。
1.4、非阻塞
非阻塞与阻塞相对,在不能立刻得到结果之前,不会阻塞当前线程,会立刻返回

最后,阻塞和非阻塞对象不是一个绝对的概念,阻塞里也可能会融入非阻塞的处理;非阻塞里也可能融入阻塞的处理

二、Okhttp同步请求

2.1 优缺点

同步请求的好处在于:理解简单;实现简单;可以将响应结果返回做后续处理;

同步请求的缺点在于:不能支持高并发的情况,在对请求响应实时性要求不高的场景下,会影响性能

2.2 实现方式

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;
}

三、Okhttp异步请求

3.1 优缺点
异步请求的好处在于:支持高并发的接口请求

异步请求的缺点在于:实现复杂;响应结果只能输出,不能返回,不利于接口的后续处理

3.2 实现方式

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 具体执行流程图

3.4 相关知识点介绍

3.4.1 Call

Call是一个接口, 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.是源码中给出的它的作用和特点。即:(1)一个被准备执行的请求;(2)可以被取消;(3)一个call只能负责去执行一个请求

由于 OkHttpClient 实现了 Call.Factory,因此它具备创建 Call 对象的功能;
OkHttpClient部分源码:

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(Call 的实现类)

RealCall真正实现了Call接口类内部定义的同步与异步请求、取消请求等方法

RealCall的关键属性

  • OkHttpClient client:就是我们创建的OkHttpClient对象,通过这个对象调用Dispatcher 去分发请求任务;
  • boolean executed:一个Call只能被执行一次就是这个属性控制的,为true时表示请求已经被执行
  • Request originalRequest:originalRequest对象是通过OkHttpClient.newCall(request) 传入的 Request 对象,这个对象在整个网络请求中起了非常重要的作用,它会被传入到各个 Interceptor 中。例如用户创建的 Request 对象,只是简单地设置了 url ,method,requestBody 等参数,但是想要发送一个网络请求,这样简单地配置还是不够的,系统提供的拦截器 BridgeInterceptor 就是负责做这件事,它会为该请求添加请求头,例如 gzip,cookie,content-length 等,简单说它会将用户创建的 request 添加一些参数从而使其更加符合向网络请求的 request 。其他拦截器的功能也是对 request 进行操作

3.4.3 Dispatcher(异步任务分发器)

异步请求时才会用到Dispatcher,它会内部指定线程池去执行异步任务,并在执行完毕之后提供了finish方法结束异步请求。然后从等待队列中获取下一个满足条件的异步任务去执行。

源码:

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

Dispatcher 的关键属性,这些属性会影响异步请求的执行

  • int maxRequests = 64:指定并发请求的最大个数
  • int maxRequestsPerHost = 5:每个主机最大请求数为5 ,也就是最多 5 个call共用一个 host。这个host 就是在 RealCall 中通过 originalRequest.url().host() 去获取的,例如 www.baidu.com
  • ExecutorService executorService:执行异步任务的线程池,在内部已经指定好了线程池,当然也可以在 Dispacther 中通过构造方法去指定一个线程池
  • Deque readyAsyncCalls:表示在队列中已经准备好的请求
  • Deque runningAsyncCalls:表示队列中正在执行的异步请求,包括已经取消的请求(还没有执行finish操作的请求)
  • Deque runningSyncCalls:正在运行的同步请求,包括已经取消的请求(还没有执行finish操作的请求)

3.4.4 AsyncCall

AsyncCall类是定义在RealCall类中的内部类,表示一个异步请求。在 Dispatcher 中分发的异步请求任务就是 AsyncCall,Dispatcher会从线程池中指定某个线程去执行AsyncCall任务。在 Dispatcher 内部定义了两个队列来存储 AsyncCall,分别是 readyAsyncCalls 和
runningAsyncCalls。它们分别表示准备要执行的 AsyncCall 队列和正在执行的 AsycnCall 队列;当然还有一个 runningSyncCalls 这个队列,但是它适用于存放 RealCall ,也就是用于存储同步请求的任务。

四、解决不能返回异步请求的响应结果问题

参考博客:
Okhttp之同步和异步请求简单分析
OKHTTP异步和同步请求简单分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值