OkHttp总结 (二) --分发器(Dispatcher)

1、OkHttp总结(一)–简单使用
2、OkHttp总结(二)–分发器(Dispatcher)

OkHttp总结(一)–简单使用 中,我们简单介绍了OkHttp以及它的简单应用,从本篇文章开始,我们将从OkHttp的分发器入手逐步地分析OkHttp的源码结构。

一、OkHttpClient 详解

OkHttpClient是OkHttp的入口,大多数的调用都是从该类开始的。
1、在OkHttpClient类内部包含了很多对象,例如:Dispatcher、List< ConnectionSpec >、CookieJar、Cache、ConnectionPool等等。其实这个类包装了很多OKHttpClient的功能模块,让该类单独提供对外的API,这种外观模式的设计十分的优雅(外观模式)。
2、而内部模块比较多,就使用了Builder模式(建造器模式)(该段出自http://www.jianshu.com/p/82f74db14a18)。

二、Request和Response 详解

1、Request
Request是对所有请求的封装,首先看一下这个类定义的字段和构造函数:

// 请求的URL地址
private final HttpUrl url;
// 请求方法(get、post等)
private final String method;
// 请求头
private final Headers headers;
// 请求体
private final RequestBody body;
// 标签
private final Object tag;
// 对于缓存的控制
private volatile CacheControl cacheControl;

private Request(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;
}

从上诉代码中我们可以看出,Request类的构造也用到了建造者模型,通过Builder来构造自身,那么我们接着看一下Request.Builder类:

private HttpUrl url;
private String method;
private okhttp3.Headers.Builder headers;
private RequestBody body;
private Object tag;

public Builder() {
     this.method = "GET";
     this.headers = new okhttp3.Headers.Builder();
}

从上述代码可以看出,OkHttp默认是以get方式进行请求的。对于一个请求最主要看三点:请求行、请求头和请求实体。
(1)请求行:请求行是由请求协议、请求方法和组成的,这几个在Buidler中已经很明确了;
(2)请求头:请求头来说明服务器要使用的附加信息,例如是否可以缓存、缓存的有效时间、数据采用什么数据结构等等,在请求中主要以键-值对的形式出现。在OkHttp中主要利用Headers类进行描述。Headers也是利用建筑者模式,内部维护一个private final String[] namesAndValues; String类型的数组,在这个数组当中,偶地址放的是key字段,在其下个地址(奇地址)放的是这个key值对value值。
(3)请求实体:请求实体是请求中要发给服务端的数据。在OkHttp中,主要由RequestBody来实现,它是是一个抽象类,有两个实现类分别是FormBody (表单提交的)和 MultipartBody(文件上传),分别对应两种不同的MIME类型FormBody :”application/x-www-form-urlencoded”和MultipartBody:”multipart/”+xxx。

2、Response
Reponse是对所有响应的封装。Response包括Headers和RequestBody,而ResponseBody是abstract的,他也有两个子类:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。

三、Dispatcher 详解

Dispatcher的创建时机是在一个OkHttpClient实例化的时候,每一个OkHttpClient对象都会持有一个Dispatcher对象,下面首先让我们看一看Dispatcher定义的字段及构造函数:

// 同时运行的请求数量的最大值(同通过set()方式更改)。
private int maxRequests = 64;
// 同时请求相同主机的请求数量最大值(同通过set()方式更改)。
private int maxRequestsPerHost = 5;
// 请求都执行完成后的回调。
private Runnable idleCallback;
// 线程池。
private ExecutorService executorService;
// 异步请求准备运行队列。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque();
// 异步请求运行队列。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque();
// 同步请求运行队列。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque();

public Dispatcher(ExecutorService executorService) {
        this.executorService = executorService;
}

public Dispatcher() {
}

通过上诉代码,不难发现,对于Dispatcher是通过三个不同的双向队列来对请求进行同于的管理。对与同步请求,直接在调用线程去执行;而对于异步请求则需要将请求放入到线程池中,并在调用线程中等待结果的回调。

对于线程池,我们在这里不做过多的介绍和分析,我们只是看一下默认的线程池:

public synchronized ExecutorService executorService() {
    if(this.executorService == null) {
            this.executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return this.executorService;
}

从构造中可以看出,该线程池是个无限大的线程池,也就是说当我们将一个任务丢给线程池的时候,若此时没有可以利用的线程就单独开一个线程去执行;当这个任务执行完以后,该线程不会被立即回收掉,而是会持续60s,若这60s内仍没有新的任务才会被线程池回收。

四、Call 详解

无论是用同步请求还是异步请求,在进行请求之前,我们都会通过OkHttpClient对象和Request对象创建一个Call对象。
首先让我们看一下Call这个接口:

public interface Call {
    // 返回当前Call对象所绑定的请求。
    Request request();

    // 返回当前Call对象所绑定的请求。
    Response execute() throws IOException;

    // 发起异步网络请求。
    void enqueue(Callback var1);

    // 关闭当前请求。
    void cancel();

    // 判断当前请求是否在执行。
    boolean isExecuted();

    // 判断当前请求是否被关闭。
    boolean isCanceled();

    public interface Factory {
        Call newCall(Request var1);
    }
}

该接口中各个函数的功能请参照注解。其中,我们会发现在接口Call的内部还有一个内部接口Factory,该接口用来返回一个Call对象。在OkHttpClient类的定义中实现了该接口,并在我们进行网络请求的时候,调用了newCall()方法返回了一个Call对象。

Call call = okHttpClient.newCall(request);

进入newCall():

public Call newCall(Request request) {
    return new RealCall(this, request);
}

其内部利用OkHttpClient对象和对应请求对象创建了一个RealCall对象并返回。RealCall是Call对象在OkHttp中的唯一实现类(注:AsyncCall不是Call的实现类,实际上它实现了Runnable的接口,它只是RealCall的内部类,为了给Call提供异步操作)。

五、发起网络请求

在我们得到了Call对象以后,就可以通过execute()和enqueue()两个方法发起网络请求了。

1、首先,让我们先看一下同步请求方法:execute()。
在我们调用execute()的时候,实际上是调用RealCall的execute(),先上源码:

    public Response execute() throws IOException {
        synchronized(this) {
            if(this.executed) {
                throw new IllegalStateException("Already Executed");
            }
            // 线程同步方法,置执行标记位,指示当前同步请求进入执行状态。
            this.executed = true;
        }

        Response var2;
        try {
            // 1、调用dispatcher的executed()。
            this.client.dispatcher().executed(this);
            // 2、在当前进程直接进行网络的请求。
            Response result = this.getResponseWithInterceptorChain();
            var2 = result;
        } finally {
            // 3、请求结束,通知dispatcher。
            this.client.dispatcher().finished(this);
        }
        return var2;
    }

在RealCall的execute()中主要进行了三步操作:
第一步:调用dispatcher的executed();
第二步:在当前进程直接进行网络的请求;
第三步:请求结束,通知dispatcher。

先看一下dispatcher的executed():

synchronized void executed(RealCall call) {
        this.runningSyncCalls.add(call);
}

方法很简单,就是将当前的call对象加入到正在执行的同步请求队列中。
在Dispatcher中维护了三个双向队列结构runningSyncCalls、readyAsyncCalls和runningAsyncCalls。其中runningSyncCalls对应正在执行的同步请求队列;readyAsyncCalls对应正在等待执行的异步请求队列;runningAsyncCalls对应正在执行的异步请求队列。

getResponseWithInterceptorChain()的方法我们暂时不做讨论,会在后续的文章中进行详细的解析。

最后我们看一下this.client.dispatcher().finished(this);

    void finished(RealCall call) {
        this.finished(this.runningSyncCalls, call, false);
    }

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized(this) {
            // 1、从对应队列中移除该请求。
            if(!calls.remove(call)) {
                throw new AssertionError("Call wasn\'t in-flight!");
            }

            // 2、进行异步请求中,准备运行队列的请求放入到运行队列中。对于同步请求promoteCalls的这个参数为false,并不执行if语句块中的操作。
            if(promoteCalls) {
                // 该操作会在异步请求当中进行详细分析。
                this.promoteCalls();
            }

            // 统计当前正在运行请求总数(包括同步请求和异步请求)。
            runningCallsCount = this.runningCallsCount();
            idleCallback = this.idleCallback;
        }
        // 若当前运行总数为0,则进行对应的回调。
        if(runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

2、异步请求方法:enqueue()

public void enqueue(Callback responseCallback) {
        synchronized(this) {
            if(this.executed) {
                throw new IllegalStateException("Already Executed");
            }
            // 线程同步方法,置执行标记位,指示当前请求进入执行或者准备执行的状态。
            this.executed = true;
        }
        this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}

在我们进行异步网络请求的时候,是直接将请求交给dispatcher()进行处理。进入到dispatcher().enqueue()继续分析:

synchronized void enqueue(AsyncCall call) {
            // 根据两个条件进行判断,当前请求是放入异步运行队列还是放入到异步等待运行的队列当中。
            // 条件1:当前运行队列中的总数量是否小于所规定的最大值;
            // 条件2:根据请求call的host,计算出当前运行队列中请求host主机的总数量,判断该数量是否小于所规定的最大值。
            if(this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
            // 将当前请求放入到异步请求运行队列当中。
            this.runningAsyncCalls.add(call);
            // 放入到线程池当中进行请求。
            this.executorService().execute(call);
        } else {
            // 将当前请求放入到异步请求准备运行队列当中。
            this.readyAsyncCalls.add(call);
        }
}

在将call放入到线程池之后。实际上执行的是call的execute(),即AsyncCall类的execute():

protected void execute() {
            boolean signalledCallback = false;

            try {
                // 进行真正的请求,和同步方式一样,后续进行详细的分析。
                Response e = RealCall.this.getResponseWithInterceptorChain();

          // 重定向关闭。在重定向拦截器时会详细讨论。  
              if(RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
          // 置标记位,并回调Callback.onFailure()。
            signalledCallback = true;
            this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                } else {
                // 重定向成功。置标记位,并回调Callback.onResponse()。
                    signalledCallback = true;
                    this.responseCallback.onResponse(RealCall.this, e);
                }
            } catch (IOException var6) {
                if(signalledCallback) {
                    Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var6);
                } else {
                    this.responseCallback.onFailure(RealCall.this, var6);
                }
            } finally {
// 请求结束,通知dispatcher()。
              RealCall.this.client.dispatcher().finished(this);
            }
}

再来看看dispatcher.finished()(这个函数与同步调用的dispatcher.finished()不同,属于重载方法,传入的参数类型不同)

void finished(AsyncCall call) {
        this.finished(this.runningAsyncCalls, call, true);
}

此时的调用和同步请求的调用相同,只不过中间多了调用promoteCalls()的过程。

    private void promoteCalls() {
        // 判断当前运行的总数是否小于所规定的最大值。小于则进入if代码块。
        if(this.runningAsyncCalls.size() < this.maxRequests) {
            // 当前异步等待队列是否为空,不为空进入if代码块。
            if(!this.readyAsyncCalls.isEmpty()) {
                // 获取异步等待队列的遍历器
                Iterator i = this.readyAsyncCalls.iterator();
                // 进入do-while循环
                do {
                    // 遍历完毕返回。
                    if(!i.hasNext()) {
                        return;
                    }

                    AsyncCall call = (AsyncCall)i.next();
                    // 当前遍历请求的主机数量是否小于最大值,小于则进入if代码块。
                    if(this.runningCallsForHost(call) < this.maxRequestsPerHost) {
                        // 首先,先将该消息从异步准备请求队列当中移除。
                        i.remove();
                        // 然后,再将该请求放入异步运行队列当中并且开始执行。
                        this.runningAsyncCalls.add(call);
                        this.executorService().execute(call);
                    }
                } while(this.runningAsyncCalls.size() < this.maxRequests);// 判断当前运行的请求总数量是否小于所规定的最大值,若小于则进入do语句块继续循环,若不满足跳出循环。
            }
        }
    }

dispatcher通过promoteCalls(),在一个异步请求结束后,将符合条件的请求从准备运行队列放入运行队列中去执行。

六、总结

通过上述分析,我们可以了解到OkHttp的请求是从OkHttpClient类开始的,在这类封装了OKHttp的大多数功能,比如分发器、连接池等。在OkHttp中分别对Request和Response进行了封装,并通过Request对象构建了Call对象,而真正的请求是由分发器进行维护和分发的。对于同步请求,分发器直接在调用的线程中进行请求;而对于异步请求,主要是利用线程池进行,在请求结束后对准备请求的队列进行筛选,将合适的请求放入到运行队列当中并对异步请求进行相应的回调,通知调用者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值