「Android 路线」| OkHttp 分发器

点赞关注,不再迷路,你的支持对我意义重大!

Hi,我是丑丑。本文「Android 路线」| 导读 —— 从零到无穷大 已收录。这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)

前言

  • 网络请求是 App 中非常重要的一个组件,而 OkHttp 作为官方和业界双重认可的解决方案,其学习价值不必多言;
  • 在这篇文章里,我将分析 OkHttp 分发器 & 拦截器 的实现原理。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。

目录


1. 前置知识

Editting...


2. OkHttp 简介

OkHttp 是 Square 开源的网络请求框架,自从 Android 4.4 移除 HttpURLConnection 后,逐渐演变成 Android 端最主流的网络请求框架。

  • OkHttp Github 仓库
  • OkHttp 官网

2.1 优点

  • 1、支持 Http1、Http2、Quic 以及 WebSocket 等多种应用层协议;
  • 2、连接池复用底层 TCP 连接(Socket),降低了请求时延;
  • 3、无缝支持 GZIP 减少数据流量;
  • 4、缓存响应数据,减少了重复网络请求;
  • 5、自动失败重试、自动重定向。

2.2 使用流程

一次 OkHttp 请求过程最少需要用到 OkHttpClient、Request、Call、Response,例如:

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url(url)
    .build();

同步请求
Call call = client.newCall(request);
获得响应
Response response = call.execute();

异步请求
Call call = client.newCall(request);
call.enqueue(new Callback(){

    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) {
        获得响应
    }

});

需要注意的是,Call 是一个接口,OkHttpClient#newCall(...)返回的其实是它的实现类 RealCall:

OkHttpClient.java

public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false);
}

RealCall.java

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

3. Dispatcher 分发器

Call#execute() & Call#enqueue(...) 分别表示调用同步请求和异步请求,客户端对请求任务的调度过程是不感知的。这是因为 OkHttp 内部的 「Dispatcher 分发器」 封装了整个请求任务的调度过程,这一节我们来具体分析下。

3.1 自定义 Dispatcher 分发器

Dispatcher 主要成员变量如下:

Dispatcher.java

异步请求最大并发数
private int maxRequests = 64;

同一域名请求最大并发数
private int maxRequestsPerHost = 5;

闲置任务(没有请求时执行)
private Runnable idleCallback;

线程池
private ExecutorService executorService;

异步请求等待队列
private final Deque readyAsyncCalls = new ArrayDeque();

异步请求执行队列
private final Deque runningAsyncCalls = new ArrayDeque();

同步请求执行队列
private final Deque runningSyncCalls = new ArrayDeque();

其中,以下几个变量是允许自定义的:

变量 描述 默认值
maxRequests 异步请求最大并发数 64
maxRequestsPerHost 同一域名请求最大并发数 5
idleCallback 闲置任务 null
executorService 线程池 无等待,最大并发

提示: 限制请求数是为了避免客户端和服务器的负载过大(Linux 一切皆文件,Socket 占用文件文件句柄,打开文件数是有限制的),至于为什么默认值为 64 和 5,据说 OkHttp 是参考了主流浏览器得出的经验值,未查及论据。

自定义的 Dispatcher 对象通过 OkHttpClient.Builder 构建者设置:

OkHttpClient.java

public OkHttpClient.Builder dispatcher(Dispatcher dispatcher) {
    if (dispatcher == null) {
        throw new IllegalArgumentException("dispatcher == null");
    } else {
        this.dispatcher = dispatcher;
        return this;
    }
}

3.2 同步请求

这一节我们来分析 OkHttp 同步请求的执行过程:

RealCall.java

已简化
public Response execute() throws IOException {
    1、禁止重复执行
    synchronized(this) {
        if (this.executed) {
            throw new IllegalStateException("Already Executed");
        }
        this.executed = true;
    }

    2、记录
    this.client.dispatcher().executed(this);
    3、执行请求,阻塞等待响应返回
    Response result = this.getResponseWithInterceptorChain();
    4、移除记录
    this.client.dispatcher().finished(this);

    return result;
}

Dispatcher.java

记录到 runningSyncCalls 中
synchronized void executed(RealCall call) {
    this.runningSyncCalls.add(call);
}

从 runningSyncCalls 中移除
void finished(RealCall call) {
    内部逻辑见 第 3.3 节
    finished(runningSyncCalls, call);
}

同步请求是在当前线程阻塞执行,所以不需要 Dispatcher 调度任务,Dispatcher 内部也仅仅是通过 runningSyncCalls 记录同步请求。

同时可以看到,RealCall#getResponseWithInterceptorChain() 是真正执行请求的地方,我一并在 「Android 路线」| OkHttp 拦截器 中分析。

3.3 异步请求

这一节我们来分析 OkHttp 异步请求的执行过程:

RealCall.java

已简化
public void enqueue(Callback responseCallback) {
    1、禁止重复执行
    synchronized(this) {
        if (this.executed) {
            throw new IllegalStateException("Already Executed");
        }
        this.executed = true;
    }
    2、调用分发器
    this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}

OkHttp 异步请求就需要 Dispatcher 登场了,我们来看看:

Dispatcher.java

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

        if (!call.get().forWebSocket) {
            2.2 复用同一个计数器(记录同一域名异步请求数)
            AsyncCall existingCall = findExistingCallWithHost(call.host());
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
        }
    }
    2.3 触发或执行请求
    promoteAndExecute();
}

主要的逻辑应该在 Dispatcher#promoteAndExecute() 中:

2.3 触发或执行请求
private boolean promoteAndExecute() {
    List executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        1、遍历 readyAsyncCalls 列表
        for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();

            2、判断全部异步请求并发数是否超过限制
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            3、判断同一域名异步请求并发数是否超过限制
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

            4、符合执行条件
            i.remove();
            4.1、同一域名异步请求数加一
            asyncCall.callsPerHost().incrementAndGet();
            4.2 可执行任务列表
            executableCalls.add(asyncCall);
            4.3 执行中任务列表
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    5 处理可执行任务列表
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        执行请求
        asyncCall.executeOn(executorService());
    }

    6 返回是否正在执行请求(若无请求,则执行闲置任务 idleCallback)
    return isRunning;
}

可以看到,异步请求是存在限制的,只有 「全部异步请求并发数不超过限制」 & 「同一域名异步请求并发数不超过限制」,才允许执行,否则会停留在 readyAsyncCalls 中等待。

那么,谁来拯救在 readyAsyncCalls 中等待的请求呢?其实就是看什么时候会触发promoteAndExecute()方法了,除了 setMaxRequests(int)、setMaxRequestsPerHost(int)外,在异步请求完成后,让出 “空闲名额” 也会触发。

AsyncCall.java

5 处理可执行任务列表
void executeOn(ExecutorService executorService) {
    boolean success = false;
    try {
        5.1 线程池执行
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        5.2 线程池拒绝执行
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        5.3 finish
        if (!success) {
            client.dispatcher().finished(this); // This call is no longer running!
        }
    }
}

5.1 线程池执行(已简化)
protected void execute() {
    try {
        5.1.1 执行请求,阻塞等待响应返回
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
            5.1.2 请求取消
            responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
            5.1.3 请求成功
            responseCallback.onResponse(RealCall.this, response);
        }
    } catch (IOException e) {
        5.1.4 请求失败
        responseCallback.onFailure(RealCall.this, e);
    } finally {
        5.1.5 finish
        client.dispatcher().finished(this);
    }
}

这段代码不算复杂,AsyncCall 是 Runnable 的子类,Runnable#run()最终会走到AsyncCall#execute(),主要分为三步:

  • 调用getResponseWithInterceptorChain()来获得请求响应,见 「Android 路线」| OkHttp 拦截器;
  • 调用 responseCallback 回调;
  • 调用 Dispatcher#finished()。

Dispatcher#finished() 我们在 第 3.2 节 同步请求里见过,现在我们来分析下:

Dispatcher.java

5.1.5 finish 或 5.3 finish
private  void finished(Deque calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
        1、从移除请求执行列表中移除
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        idleCallback = this.idleCallback;
    }

    2、触发或执行请求
    boolean isRunning = promoteAndExecute();

    3、若无请求,则执行闲置任务 idleCallback
    if (!isRunning && idleCallback != null) {
        idleCallback.run();
    }
}

4. 分发器线程池

为什么不能使用ArrayBlockingQueue()
为什么不能使用LinkedBlockingQueue
为什么使用SynchronousQueue


5. 总结

看到这里,我们先来总结这篇文章的内容以及遇到的疑问:

  • 1、同步请求不需要 Dispatcher 任务调度,Dispatcher 只做记录;
  • 2 、异步请求有限制,被限制的请求会在 ready 队列中等待,直到有请求完成让出 “空闲名额” 才会触发执行;

在前面关于同步请求和异步请求的分析中,我们都提到了 RealCall#getResponseWithInterceptorChain() 是真正执行请求的地方,我在 「Android 路线」| OkHttp 拦截器 里讨论。


创作不易,你的「三连」是丑丑最大的动力,我们下次见!

你可能感兴趣的:(「Android 路线」| OkHttp 分发器)