httpClient.newCall(request).enqueue(getCallback(jsonObject, ABConstant.REQUEST_URL_LOGOUT, handler));
从一句代码可以引申出一堆东西,累了。
目录
1)创建OkHttpClient
2)创建Request
3)调用newCall
4)调用异步请求enqueue()
RealCall类的enqueue方法
5)进入底层架构——分发器
1.异步请求底层流程
1.队列分配(线程执行前)
2.队列取出任务,执行任务(线程准备执行)
3.队列移动(线程执行后)
2.线程池的定义
我分为了四个部分,橙色第一部分实例化一个OkHttoClient类对象就可以了。
所有的逻辑大部分在拦截器Interceptors中,但进入拦截器之前还要靠分发器来调配请求任务。
分发器Dispatcher:内部维护队列和线程池,完成请求调配。
拦截器Interceptors:完成整个请求。
先创建一个OkHttpClient类的对象(httpClient)
再创建一个Request类的对象,用构建器模式Bulider创建(request)
OkHttpClient类有newCall方法,需要传入一个Request类的对象的参数,也就是第二步创建出来的request。
细节:
至此,我们完成了绿色第二部分,创建了一个Call对象。
在RealCall类中调用enqueue()方法,参数是CallBack,也就是要传入实现该接口的类的实例化对象,这里可以用匿名内部类,然后重写该接口中的onFailure()和onResponse()方法。
那么在RealCall类中调用enqueue()方法执行了什么呢?
httpClient.dispatcher().enqueue(new AsyncCall(CallBack));
AsyncCall:
可以看做一个请求任务,把CallBack对象再次包装。AsyncCall是一个RealCall的内部类,构造参数是一个Callback对象,该CallBack对象里有实现该接口的两个方法onFailure()和onResponse()。
为什么要包装起来呢?
因为AsyncCall类实现了Runnable接口,可以有线程。
包装完CallBack,就可以进入下图了。
至此,我们完成了紫色第三部分,重写了RealCall类的enqueue()。
当然,你其实也已经完成了蓝色第四部分,你只需要new CallBack接口,然后重写里面的两个方法就好了。
你这个CallBack参数相当于一个任务,先被写到了run()方法中,然后被new了个AsyncCall包装了起来,传送给分发器的enqueue(),就是下图流程了。
如果还想看后面就是底层了,因为RealCall类重写的enqueue()方法调用了底层的,所以就顺着这方法往下看了。
然后就顺着RealCall类重写的enqueue方法进入我们一般接触不到的分发器的enqueue()咯~~~~
在分发器Dispatcher类中,有三个属性引人注目,也就是有三个队列,先入先出。
//readyAsyncCalls队列:(准备执行的异步请求队列)
Deque readyAsyncCalls=new ArrayDeque<>();;
//runningAsyncCalls队列:(正在执行的异步请求队列)
Deque runningAsyncCalls=new ArrayDeque<>();
//runningSyncCalls队列:(正在执行的同步请求队列)
Deque runningSyncCalls=new ArrayDeque<>();
为什么用ArrayDeque呢?
看数据结构去。
首先在RealCall的enqueue()方法调用了Dispatcher类的enqueue()方法,并把传入enqueue()方法的参数AsyncCall对象放入到ready队列或者running队列中。
但是该放入哪个队列呢?
当AsyncCall任务放入正在执行的异步请求队列中,就会加入到线程池中执行。
所以————
不符合以上条件任何一个,则会放入readyAsyncCalls队列。
executorService().execute(call);//线程池跑任务
既然是异步请求,肯定要用子线程来实现这个请求才叫异步请求。
当放入正在执行的异步请求队列中,Dispatcher会使用它内部维护的一个线程池去调用子线程对象去跑请求任务。
execute()的参数要一个实现了Runnable接口的类的实例化对象即 包装CallBack的AsyncCall对象。
而AsyncCall内部final类直接继承了NamedRunnable类,NamedRunnable类实现了Runnable接口。就等于AsyncCall内部类间接实现了Runnable接口。
所以NamedRunnable类重写了run()方法,run()方法里只有一个execute()方法,所以会执行execute()方法,但是该方法是NamedRunnable类里面的一个抽象方法,还没有方法体。
所以!!!!
在AsyncCall内部final类会重写NamedRunnable类的抽象execute()方法,也就是说execute()方法在run()方法中,而run()方法会在线程池中执行。
所以得把Callback包装成AsyncCall才能放入到底层队列以及线程池之中运行,就是因为AsyncCall间接实现了Runnable接口才能被线程池调用父类里面的run方法,然后运行execute方法时动态绑定到子类重写的方法。
从readyAsyncCalls队列移动到runningAsyncCalls队列的条件是什么?
在AsyncCall内部类重写的execute()方法,就是线程执行的方法。在线程每执行完一个任务后(一个任务=一个execute方法),无论请求成功或者失败,finally代码块都会执行以下语句。
finally{
httpClient.dispatcher().finished(this);//this指当前的AsyncCall对象
}
那么Dispatcher类的finished方法里面有什么呢?
那么Dispatcher类的promoteCalls方法里面有什么呢?
这个方法其实就是从ready队列获取任务放入running队列执行。
1.判断runningAsyncCalls队列的元素个数是否大于或等于最大个数,是则return。
2.判断readyAsyncCalls队列是否为空,是则return。
3.for循环:
1)遍历readyAsyncCalls队列,条件是:该任务元素和running队列中同一Host不大于最大数的情况下
2)那么该任务元素就从readyAsyncCalls队列去掉,并加入runningAsyncCalls队列,并执行下面的方法。
3)如图:
executorService().execute(call);//线程池跑任务
最后再判断runningAsyncCalls队列满了没,没满的话,反复执行For循环3个步骤,即可完成从readyAsyncCalls队列到runningAsyncCalls队列的移动,即队列移动。
在分发器Dispatcher类中,有一个这样的方法,这个方法内部就是创建线程池的
public synchronized ExecutorService executorService(){
if(executorService==null){
executorService=new ThealPoolExecutor(....)
}
return executorService;
}
线程池和拦截器懒得写了,lay了。。
整体流程:
创建RealCall对象>>重写RealCall类enqueue()>>调用Dispatcher类enqueue()来调配任务(队列分配)>>线程池执行running队列>>线程执行完毕队列移动>>线程池执行>>队列移动。。。。。