Volley框架使用了线程池作为基础结构,主要分为主线程(UI线程)、CacheDispatcher线程和NetworkDispatcher线程。其中主线程和CacheDispatcher线程都只有一个,而NetworkDispatcher线程可以有多个,默认为4个,也就是在初始化Volley框架后会在后台默认运行这5个线程,并且所有的网络请求都会复用这5个线程。
通过上面的流程图,Volley框架的整个运行流程就很清晰了。回顾一下上一章写的Volley框架的使用方式,创建RequestQueue对象à创建Request对象à将Request对象加入到RequestQueue对象中。将Requet对象加入到RequestQueue对象中后,将执行的流程如下:
Volley框架主要流程源码主要是Volley、RequestQueue、CacheDispatcher、NetworkDispatcher、DiskBasedCache、BasicNetwork、ExecutorDelivery、Request。
Volley.java中的核心代码,该类主要是运用组合模式,简化了RequestQueue的构建过程。RequestQueue的创建依赖了2个对象,一个是硬盘缓存Cache策略,一个访问网络能力。
硬盘缓存Cache(DiskBasedCache)其主要功能是提供将Cache线程中缓存的request请求给持久化保存在硬盘中,路径为“应用缓存路径”+“/”+“volley”。DiskBasedCache中定义了一个LinkedHashMap来保存需要缓存的request请求,并且同时将所以需要缓存的request的请求保存在硬盘中,路径为“应用缓存路径”+“/”+“volley”,对应的文件名为request.cachekey(request请求的url)的hashcode值,默认缓存大小为5MB。在这里通过扩展实现我们自定义的Cache可以实现我们需要的缓存策略。
访问网络能力其主要功能是定义网络访问的方式,默认的在sdk>8时,Volley框架是使用HttpURLConnection进行访问请求,在sdk<=8时,Volley框架是使用HttpClientStack进行访问请求,在这里通过扩展实现我们自定义的HttpStack可以实现Https或者其他的网络访问方式。
RequestQueue.java提供了3个构造函数,在实例化RequestQueue.java是Cache(缓存策略)和Network(网络访问能力)为必填函数,threadPoolSize(开启的网络请求线程数)为选填,默认为4,ResponseDelivery(传递request和response)为选填,默认传递给主线程。
RequestQueue.java的初始化入口,在start函数中初始化了一个CacheDispatcher线程和4个(默认)NetworkDispatcher线程。在CacheDispatcher线程中不执行实际的网络请求,只会判断在缓存中的request是否满足当前的request需要(是否存在、是否有限期内),对于满足的request请求直接从缓存中取请求结果,对于不满足的request请求,则将request请求加入到网络请求队列。在NetworkDispatcher线程将当前的request请求执行网络请求,在得到请求结果后,解析结果为response,判断request是否允许缓存,允许则将请求和请求结果缓存在Cache中。
RequestQueue.java的request加入入口,调用此函数,会先判断request请求是否允许缓存,不允许则加入网络请求队列,如果允许则判断当前的request请求队列中是否包含此请求(同一个url请求多次),如果有则加入等待队列(mWaitingRequests)key值为request.getCacheKey(request的url),等待CacheKey(request的url)相等的第一个request申请结束时将该CacheKey(request的url)对应的等待request的list全部加入缓存请求队列,如果没有则在等待列队(mWaitingRequests)加入一个该request请求的cacheKey的记录,并将该请求加入缓存请求队列。通过该策略会大大的减少网络请求的次数,增加请求结果的复用性,提高网络请求效率。
CacheDispatcher.java的构造函数包含了4个参数,缓存请求队列、网络请求队列、缓存策略、请求结果传递策略。
CacheDispatcher线程的主要run方法,在线程启动时,先调用cache策略的init方法,保存cache策略正常运行并将上一次缓存到硬盘的request请求读入内存中缓存(保证应用退出后内存缓存释放后request请求还没有过有限期却因内存缓存丢失而去增加网络,增加应用的网络请求代价与风险)。执行完ceche策略的初始化后,CacheDispatcher线程将循环的从缓存请求队列(PriorityBlockingQueue,线程安全、阻塞、无界)中取出request请求,根据request请求从Cache中查找对应的缓存请求结果,如果缓存有效则,将结果传递给主线程,不然则加入到网络请求队列。
NetworkDispatcher.java的构造函数包含了4个参数,网络请求队列、网络请求能力、缓存策略、请求结果传递策略。
NetworkDispatcher线程的run函数,在该函数中首先从网络请求队列(PriorityBlockingQueue,线程安全、阻塞、无界)取出request请求,使用网络请求能力mNetwork来执行request请求,解析请求结果为response,判断request是否允许缓存,如允许则将请求结果加入缓存中,传递请求结果response给主线程。
DiskBasedCache.java提供了2个构造函数,一个是硬盘缓存文件保存路径(必填),一个是最大的文件缓存大小(选填,默认为5MB)。在DiskBasedCache中定义初始化了一个final的LinkedHashMap(初始大小为16、装载因子为0.75、排序为访问顺序),该map提供了内存缓存的能力,并且将map内的记录安装访问顺序排序。
DiskBasedCache的初始化函数,初始化函数有两个目的,一是判断路径是否存在,不存在则创建,二是读取已有的缓存到硬盘的数据到内存中(实现了持久化缓存,将缓存的生命周期只关联到其有限性,即是否过期,而不是关联到应用的生命周期),通过这个策略可以实现更加精确的请求数据的时效控制(只能服务器有关,通过服务器返回的有效时长控制,如Session、AccessToken等)。
DiskBasedCache的释放缓存策略,当已缓存的request大小+即将缓存的request大于设定的最大的缓存大小时,则执行该释放缓存侧路。该策略实际上是释放最久未使用的request,主要是依赖与定义声明的LinkedHashMap(初始大小为16、装载因子为0.75、排序为访问顺序),并且执行一次释放策略结束条件为已用的缓存大小小于设定的最大的缓存大小的0.9。在每次增加一个缓存的request之前执行该策略。
BasicNetwork.java的代码比较简单,核心代码如上,该类主要是将HttpStack提供的实际的网络访问能力做一次包装,HttpStack请求网络后得到请求结果解析为统一的HttpResponse,而BasicNetwork将HttpResponse结果做一个更细致的处理,将无效的结果和有效的结果分别进行包装处理,最终处理包装为NetworkResponse供Request进行最后一次解析处理。
ExecutorDelivery.java只提供一个功能,即将请求的结果response传递给主线程,然后在主线程中的handler中执行最后请求结果的回调。(对于还未过有效期但是需要刷新的请求,可以通过该类回调加入到网络请求队列)。
Request.java应该是跟我们使用Volley框架最相关密切的类,该类设计的东西也比较复杂,我们安装由简单到复杂的顺序一点点讲解。
在Request类中定义了2个抽象函数,所以当我们自定义自己的Request时,在自定义的Request构造函数中调用父类的构造函数传递请求方式、请求url、错误结果时的监听对象后,只需要实现这两个抽象函数就可以安装最基础的方式来使用了。其中parseNetworkResponse函数是用于将request请求得到的结果NetworkResponse解析成为自己想要的数据结果类型(如string、json、xml等),第二个函数deliverResponse是在parseNetworkResponse执行完之后执行的回调函数,即将request请求解析后的结果回调给主线程调用的地方。所以在我们自定义Request时,都会在构造函数中携带一个监听对象,该对象主要用于回调request请求结果。
通过复写父类Request中的getHeaders和getParams函数,我们可以自定义的为该request请求设置请求头和请求参数。
通过复写父类Request中的getBodyContentType函数,我们可以自定义的为该request请求设置请求体的格式以及编码。
通过调用父类Request中的setShouldCache函数,我们可以自定义的设置该request请求是否允许缓存。
通过复写父类Request的getPriority函数,我们可以自定义的设置该request请求的优先级。在Request中定义了4种优先级,在默认情况下定义声明的request对象为Priority.NORMAL级别。根据优先级的高低会影响request请求在请求队列中的排序,请求级别越高排序越靠前,同等优先级的排序不保存按顺序。
由于上诉同等优先级的情况下,request的排序是不保证的,那么如果我们自定义的request请求一定要按照特定的顺序时,我们可以复写父类Request的setSequence函数,为该request请求指定序列号,同等优先级下序列号越大排序越靠前。
通过调用父类Request的setRetryPolicy函数,可以设置该request请求的超时时间和重试次数。setRetryPolicy函数需要接受一个RetryPolicy的参数,RetryPolicy是接口类型,所以我们需要实现该接口即可。