OkHttp3源码解析(一)分发器Dispatcher原理分析

OkHttp3源码解析(一)分发器Dispatcher原理分析
OkHttp3源码解析(二)五大拦截器原理分析

OkHttp 3.10.0版本,最新OkHttp为:4.0.1逻辑与3版本并没有太大变化,但是改为kotlin实现。

OkHttp介绍

OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。

优点:

  • 支持Spdy、Http1.X、Http2、Quic以及WebSocket
  • 连接池复用底层TCP(Socket),减少请求延时
  • 无缝的支持GZIP减少数据流量
  • 缓存响应数据减少重复的网络请求
  • 请求失败自动重试主机的其他ip,自动重定向
  • …….
异步get请求
String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
        .url(url)
        .get()//默认就是GET请求,可以不写
        .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: ");
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});
异步Post请求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(requestBody)
        .build();
okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: " + e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
        Headers headers = response.headers();
        for (int i = 0; i < headers.size(); i++) {
            Log.d(TAG, headers.name(i) + ":" + headers.value(i));
        }
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});

1、OkHttp的调用流程

image.png

OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、Response,但是框架内部进行大量的逻辑处理。

所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠分发器来调配请求任务。

  • 分发器:内部维护队列与线程池,完成请求调配;

  • 拦截器:五大默认拦截器完成整个请求过程。

2、分发器:异步请求的工作流程

Call call = client.newCall(request);
Response response = call.enqueue();

final class RealCall implements Call

=> RealCall#enqueue
=> client.dispatcher().enqueue(new AsyncCall(responseCallback));
=> Dispatcher#enqueue

当我们调用client.dispatcher().enqueue(new AsyncCall(responseCallback));的时候会进入Dispatcher的enqueue方法,

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        runningAsyncCalls.add(call);
        executorService().execute(call);
    } else {
        readyAsyncCalls.add(call);
    }
}
image.png

一开始会传一个AsyncCall到dispatcher中,在dispatcher的enqueue里,AsyncCall可能会加入到正在运行的runningAsyncCalls队列或者加入到正在等待的readyAsyncCalls队列,如果加入到runningAsyncCalls队列的话,会被进一步提交到ThreadPool线程池中,因此AsyncCall肯定是一个实现了Runnable接口的“任务”。当这个任务在线程池中执行完毕的时候,会调用dispatcher里的finished方法,在finished中又会调用到dispatcher里的promoteCalls方法,去遍历readyAsyncCalls队列,找出满足条件的任务,加入到runningAsyncCalls队列里,又开始新一轮的执行。这就是分发器里整个异步请求的工作流程。

那么问题来了:

  • 1、分发器dispatcher如何决定将请求放入runningAsyncCalls还是readyAsyncCalls?
  • 2、从readyAsyncCalls移动到runningAsyncCalls的条件是什么?
  • 3、分发器dispatcher线程池是怎么工作的?

3、源码分析

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        runningAsyncCalls.add(call);
        executorService().execute(call);
    } else {
        readyAsyncCalls.add(call);
    }
}

当正在请求的任务数量runningAsyncCalls小于maxRequests(异步请求同时存在的最大请求默认64个),这个maxRequests数量也是可以通过setMaxRequests方法来设置的,但最小不能小于1个,不然会报异常。另外一个限制是:异步请求同一域名同时存在的最大请求runningCallsForHost不能大于maxRequestsPerHost(默认是5个),当然maxRequestsPerHost也是能通过setMaxRequestsPerHost来自己重新设置的。如果满足这两个条件,就会把这个AsyncCall也就是Runnable加入到runningAsyncCalls队列中,同时加入到executorService线程池中,并且调用execute开始执行这个任务。

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

进入到这个线程池,首先核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。而最大线程Integer.MAX_VALUE与等待队列SynchronousQueue的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。一般来说,等待队列BlockingQueue有:ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue。那么为什么要使用SynchronousQueue : 无容量的队列。其实,使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合Integer.MAX_VALUE就实现了真正的无等待。

回到之前的代码,如果不满足,加入到等待队列readyAsyncCalls。那么这个Runnable真正在哪里执行它的run方法呢?

final class AsyncCall extends NamedRunnable {
public abstract class NamedRunnable implements Runnable {
    protected final String name;

    public NamedRunnable(String format, Object... args) {
        this.name = Util.format(format, args);
    }

    @Override
    public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
            execute();
        } finally {
            Thread.currentThread().setName(oldName);
        }
    }

    protected abstract void execute();
}

我们看到AsyncCall是继承自NamedRunnable,NamedRunnable又实现了Runnable接口,在NamedRunnable的run方法里调用了execute方法,execute是个抽象方法,所以它真正实现的地方就是在AsyncCall的execute方法里

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
        super("OkHttp %s", redactedUrl());
        this.responseCallback = responseCallback;
    }

    String host() {
        return originalRequest.url().host();
    }

    Request request() {
        return originalRequest;
    }

    RealCall get() {
        return RealCall.this;
    }

    @Override
    protected void execute() {
        boolean signalledCallback = false;
        try {
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
                signalledCallback = true;
                responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
                signalledCallback = true;
                responseCallback.onResponse(RealCall.this, response);
            }
        } catch (IOException e) {
            if (signalledCallback) {
                // Do not signal the callback twice!
                Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
                eventListener.callFailed(RealCall.this, e);
                responseCallback.onFailure(RealCall.this, e);
            }
        } finally {
            client.dispatcher().finished(this);
        }
    }
}

所以,我们先不去关心这个任务AsyncCall到底是怎么执行的。我们看到最后也就是任务跑完了,不管成功或者失败,一定会去执行finally里的代码块client.dispatcher().finished(this)表示这个任务结束了。所以我们先去看看dispatcher里的finished到底是怎么执行的。

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
}
private  void finished(Deque 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();
    }
}

一开始把call从calls里(也就是传进来的runningAsyncCalls)移除。然后执行promoteCalls方法

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

    for (Iterator 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.
    }
}

一开始也是判断maxRequests和maxRequestsPerHost,然后遍历readyAsyncCalls队列,把任务从readyAsyncCalls里取出加入到runningAsyncCalls里。到这里,分发器里整个异步请求的工作流程源码分析完成了。同步请求就不展开讲了,很简单,收到任务时加入runningSyncCalls队列,执行完毕从runningSyncCalls队列移除。

最后,总结一下:

  • Q1: 如何决定将请求放入ready还是running?

    如果当前正在请求数不小于64放入ready;如果小于64,但是已经存在同一域名主机的请求5个放入ready!

  • Q2: 从running移动ready的条件是什么?

    每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动!

  • Q3: 分发器线程池的工作行为?

    无等待,最大并发

你可能感兴趣的:(OkHttp3源码解析(一)分发器Dispatcher原理分析)