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 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 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.
 *  * 

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异步和同步请求简单分析

你可能感兴趣的:(Okhttp,java)