okhttp 请求流程(一)

OKhttp 网络通讯框架
  • okhttp 简介
  • okhttp 用法
  • okhttp 分发器
  • okhttp 拦截器

一、OKHttp 简介

okhttp git 地址:https://github.com/square/okhttp

1.1 简介

替代 HttpUrlConnection 和 Apache HttpClient(android API23 6.0里已移除HttpClient,现在已经打不出来)

OkHttp是一个默认高效的HTTP客户端:

1.2 okhttp 优势

允许连接到同一个主机地址的所有请求,提高请求效率

共享Socket,减少对服务器的请求次数

通过连接池,减少了请求延迟

缓存响应数据来减少重复的网络请求

减少了对数据流量的消耗

自动处理GZip压缩

支持同步异步调用

1.3 请求流程

file

二、okhttp 使用

2.1 okhttp 的集成

implementation "com.squareup.okhttp3:okhttp:4.7.2"

OkHttp包含一个用于测试HTTP、HTTPS和HTTP/2客户端的库。

testImplementation "com.squareup.okhttp3:mockwebserver:4.7.2"

2.2 okhttp 常用的请求对象

  • OkHttpClient :用来发送http 请求和 获取网络请求响应体
  • Request
  • Request.Builder
  • Call
  • ResponseBody

2.2.1 OKHttpClicent

1、简介

OkHttpClient :用来发送http 请求和 获取网络请求响应体,每个OkHttpClient 都拥有自己独立的的连接池和线程池,为了节省资源我们可以共用 线程池 和 连接池,okhttp 可设计为单例

2、okhttpClient 创建:

public final OkHttpClient client = new OkHttpClient(Builder builder)
new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor))
                .readTimeout(10, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build()

okHttpClient.Builder 常用方法:

  • connectTimeout: 设置连接超时时间,如果设置为0,意思是没有超时时间,否则必须设置1 ~ Integer.MAX_VALUE 之间的整数,吗,默认连接超时时间为 10s
  • readTimeout : 为 TCP Socket 和单个读IO 为 设置 读取超时时间,如果设置为0代表没有超时时间,否则必须设置1 ~~Integer.MAX_VALUE 之间的整数,默认是10s
  • writeTimeout : 为 单个的写IO操作 设置 写入超时时间,如果设置为0代表没有超时时间,否则必须设置1 ~~Integer.MAX_VALUE 之间的整数,默认是10s
  • proxy: 设置 http代理
  • addInterceptor : 添加拦截器
  • addNetworkInterceptor :添加网络拦截器

3、Request

请求对象:包含了HttpUrl, method, headers,RequestBody ,tag

method方法:

  • GET
  • POST
  • DELETE
  • PUT

三、Dispatcher 分发器:

网络请求执行有两种方式

  • 同步请求
  • 异步请求

请求代码:

OkHttpClicent  client = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor))
                .readTimeout(10, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build()       

Request request = new Request.Builder()
                .url("")
                .build();
         //创建 call 对象
        Call call = httpClient.newCall(request);
         //执行同步请求
        Response response = call.execute();

1、发起一个OKhttp 请求


OkHttpClicent  client = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor))
                .readTimeout(10, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build()   

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

在发起一个OKhttp 请求至少用到了三个对象, OKHttpClient ,Request ,Call 对象

httpClient 对象 通过newCall 方法 接收传入的 Request.Builder 提供的request 对象生成一个Call对象。其中 OKHttpClient 以及 Request 给我们提供了Builder 对象供我们去配置使用。

建造者模式:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。实例化OKHttpClient和Request的时候,因为有太多的属性需要设置,而且开发者的需求组合千变万化,使用建造者模式可以让用户不需要关心这个类的内部细节,配置好后,建造者会帮助我们按部就班的初始化表示对象

同时OkHttp在设计时采用的门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端OkHttpClient统一暴露出来。

Call 是一个接口,封装了将发起的 okhttp 请求的 request 对象,可以对对请求进行取消 以及执行,newCall 方法生成了一个Call接口的实现对象 RealCall

  • execute 方法:同步方法
  • enqueue 方法:异步方法

在 RealCall方法中 实现了Call 接口的 execute (这节先不管enqueue 方法),同时发起了一个网络请求,这里涉及了请求流程中的dispatcher

   @Override
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
            //分发器分发请求  添加到 readyAsyncCalls 
            client.dispatcher().executed(this); 
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } catch (IOException e) {
            eventListener.callFailed(this, e);
            throw e;
        } finally {
            client.dispatcher().finished(this);
        }
    }

3.1 同步请求

网络请求的发起控制是通过 dispatcher 来处理的,dispatcher

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

OKhttp 中 用到了两个双端队列,其中一个双端队列( runningSyncCalls )用于保存正在执行的Task,另外一个双端队列 保存准备执行的( readyAsyncCalls )任务 (readyAsyncCalls 保存了准备执行的任务),

> 双端队列:deque,全名double-ended queue 是一种具有队列和栈性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除在表的两端进行。

在 通过 executed 方法中 同步请求被 加入 名为 runningSyncCalls 的双端队列 经过一系列的拦截器,返回 Response 来获取网络数据,通过 执行tryCatch 的finally 方法 Dispatcher.finish 方法 处理返回成功是的请求移除

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

请求处理流程:

file
OkHttp 中网络请求通过Dispatcher 来分发请求,dispatcher 通过 runningSynCall 双端队列来对请求进行保存,执行dispatcher 的 executed 方法把一个请求添加到 runningSyncCalls 双端队列中,随后经过一系列的拦截操作返回 Response 对象, 当网络请求执行完成之后 通过 dispather 的finish 方法 把该请求移除双端队列。 为了保证同步 内部方法都用了synchronized 进行修饰。

3.2 异步请求

dispatcher 通过 enqueue 方法来添加一个异步请求,dispatcher 定义了 runningAsyncCalls ,readyAsyncCalls 来控制请求的添加与移除。异步请求的执行是通过 创建了一个线程池 创建Runnable 任务来执行 AsyncCall 任务 , 异步请求限制了最大request 请求个数不能超过 64 ,同一域名的请求数不能大于 5 个,这些参数可以通过 okhttpClient 提供的Builder 进行修改。

    //最大请求书
    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<>();

dispatcher 定义以上参数来对异步请求进行控制:

//runningAsyncCalls 正在运行的异步请求数量 
//runningCallsForHost(call) 统一域名的请求数量
synchronized void enqueue(AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            readyAsyncCalls.add(call);
        }
    }

当数据请求数 >= 64 的时候 请求被放入的readyAsynCalls,否则放入了runningAsynCalls

    @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));  //创建一个 AsynCall 放到 双端队列中去
    }

异步请求通过 执行queue 传入我们创建的CallBack 对象 add 到 runningAsyncCalls 如果请求数目大于 64 或者 同一个host 的请求数目大于 5 那么这个AsycCall 会被放入 runningAsynCalls ,通过 executorService() 方法创建的线程池 去执行请求 当请求经过 分发拦截器 一系列处理完成之后 调用 dispatcher 的 promoteCalls 方法 进行 readyAsyncCalls 轮询 放入 runningAsyncCalls

   private void promoteCalls() {
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();

            if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }

            if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
    }

上面提到异步请求使用线程池执行的:

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

ThreadPoolExecutor 5 个参数:

corePoolSize: 核心线程数目

maximumPoolSize:最大线程数

keepAliveTime: 当线程数大于 corePoolSize 空闲线程的存活时间

TimeUnit : 时间单位

BlockintQueue : 任务队列(阻塞队列)

线程工作机制

1、创建任务个数小于 corePoolSize 线程创建 任务个数的线程去执行任务

2、创建任务个数 等于或大于 corePoolSize ,任务会添加到 BlockQueue 队列中

3、如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务

4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()

线程的饱和策略:

  • AbortPolicy 直接抛出异常,默认策略
  • CallerRunsPolicy:用调用者所在的线程来执行任务
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
  • DiscardPolicy:直接丢弃任务

在上面看到 创建线程池的 的corePoolSize 的个数为0,而且使用到了 SynchronousQueue 无容量的队列,线程的corePoolSize 为0 无论如何任务添加到BlockQueue 都会失败,则会创建新的线程去执行提交的任务,完全没有任何等待,最大限度的保证的并发量。

讨论数量: 0

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!