编者按: 本博文主要记录我对Volley的理解以及从网上查阅的一些资料,主要用作一个学习笔记,若是有读者发现其中有理解错误之处,还望评论区帮忙指出,谢谢
Q & A:
Q: Volley为什么在2.3以前使用HttpClientStack,而在2.3以后使用HurlStack?
A: 一般来说,HttpURLConnection相对轻量级,也比较小,而Apache HTTP Client接口多,比较大,所以一般情况下HttpURLConnection是最佳选择。然而在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。另外在 2.3 HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能, 4.0 HttpURLConnection 支持了请求结果缓存。所以综上所述,对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。Q: Volley在使用HttpClient进行网络请求时会设置User-Agent字段,而使用HttpURLConnection时则不会,原因是什么?
A: 使用Fiddler/Charles抓包会发现,HttpURLConnection 默认是有 User-Agent 的,实际在请求发出之前,会检测 User-Agent 是否为空,如果为空,则加上系统默认 User-Agent。在 Android 2.1 之后,我们可以通过String userAgent = System.getProperty("http.agent")得到系统默认的 User-Agent。Volley 如果希望自定义 User-Agent,可在自定义 Request 中重写 getHeaders() 函数。
在使用HttpClient进行网络请求时,Volley会将请求头中的 User-Agent 字段设置为 App 的 ${packageName}/${versionCode},如果异常则使用 "volley/0"(这个获取 User-Agent 的操作应该放到 if else 内部更合适)。Q: Volley中为什么使用ByteArrayPool进行存储网络数据?
A: 首先可以试想一下ByteArrayPool产生的背景。当网络请求得到返回数据以后,我们需要在内存中开辟出一块区域来存放我们得到的网络数据,不论是json还是图片,都会存在于内存的某一块区域,然后拿到UI显示,然而移动客户端请求一般都是相当频繁的操作,想一下我们平时使用今日头条等一些客户端,几乎每一个操作都要进行网络请求。那么问题来了:这么频繁的数据请求,获得数据以后我们先要在堆内存开辟存储空间,然后显示,等到时机成熟,GC再回收这块区域,如此往复,那么GC的负担就会相当的重,然而Android客户端处理能力有限,频繁GC对客户端的性能是会产生很大的影响的。那么我们是否能够通过某种机制来减少内存分配次数和GC次数,从而达到提高程序性能的目的呢?我猜想这个问题便是这个类诞生的原因了。
其次就需要看这个类是如何实现这样的目的的呢?从类名可以看出,这是一个字节数组缓存池,用来缓存从网络请求中获得的数据的。ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize来完成字节数组的缓存。当需要使内存区域的时候,先从已经分配的缓存区域中获得以减少内存分配次数。当空间用完以后,再将数据返回到此缓冲区。这样,就可以减少内存区域堆内存的波动和减少GC的回收,让CPU把更多的性能留给页面的渲染,提高性能。通过这个类发现,谷歌对技术的细节十分考究。Q: Volley为什么不适合用来下载大的数据文件?
A: 因为Volley会在解析的过程中将所有的响应数据保留在内存中。对于下载大量数据的操作,请考虑使用DownloadManager。Q: 4. Volley的DiskBasedCache类会把服务器相应信息写入磁盘,然后再读磁盘取
出缓存,writeInt(),writeLong()方法为什么要进行位运算?
A: Java的IO本来就是对byte的操作,一个int占4个byte,所以需要按位写入。因为网络字节序是大端字节序,而在80X86平台中,是以小端法存放的,比如我们经过网络发送0x12345678这个整形,但实际上流是0x87654321。因为不同的平台int long等,他们的字节存储的顺序可能是不一样的。可能低位在前或者高位在前。writeInt() 和 readInt()以字节为单位,用一致的顺序读写,就能适配不同的平台。Q: RetryPolicy 如何做到重试的呢?具体流程是什么样的呢 ?
A: 首先假设你设置了重试的策略,其次performRequest外面其实是个while 循环。假设在网络请求过程中产生异常, 比如read time out, catch 这个异常的代码会看看是否重试,如果是重试,就把这个异常吞掉,然后继续下一次循环,否则,抛出异常,由上一层代码去处理。Q: 当UI线程不断的向队列添加请求,队列如果有大小限制,队列满的时候不会ui线程阻塞么?如果没有大小限制,不会导致oom么?
A: PriorityBlockingQueue:一个无界阻塞队列,PriorityBlockingQueue的默认容量是11;它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞获取操作。虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败(导致 OutOfMemoryError)。此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(这样做会导致抛出 ClassCastException)。第一个问题:不会阻塞,只有获取的时候(如果队列为空,则会阻塞);第二个问题:会导致OOM。首先,volley里面用的BlockingQueue是PriorityBlockingQueue的实现类(队列定长11),这个类有个特点:不会阻塞调用者线程,所以UI线程不会被阻塞,这是第一个问题;第二个问题,有可能会导致堆内存溢出,所以使用这个实现类,还有个要求:调用者的生产速度不能快于处理者的处理速度。Q: CacheDispatcher和NetworkDispatcher两条线程都处理同一个Cache,难道不会出现互斥现象吗?为什么不加锁?
A: 因为里边的方法都是同步的。Q: Volley实现网络缓存的机制?
A: Volley的缓存方法:根据进行请求时服务器返回的缓存控制Header对请求结果进行缓存,下次请求时判断如果没有过期就直接使用缓存加快响应速度,如果需要会再次请求服务器进行刷新,如果服务器返回了304,表示请求的资源自上次请求缓存后还没有改变,这种情况就直接用缓存不用再次刷新页面,不过这要服务器支持了。当对上次的请求进行缓存后,在下次请求时即使没有网络也可以请求成功,关键的是,缓存的处理对用户完全是透明的,对于一些简单的情况会省去缓存相关的一些事情。Q: Volley中的设计模式
A:
策略模式
含义:定义一系列的算法,把它们一个个封装起来,并且使他们可互相替换。本模式使得算法可独立于使用它的客户而变化。
适用场景:一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么可以使用策略模式避免在类中使用大量的条件语句。
Volley中的策略模式:在Volley中对于HttpStack的设计用到的就是策略模式。我们知道Android Framework里面同时包含HttpURLConnection和Apache HTTP Client 2套Http框架,HttpURLConnection相对轻量级,也比较小,而Apache HTTP Client接口多,比较大,HttpURLConnection是最佳选择,但在Android SDK小于9时,HttpURLConnection存在一些bug,所以当Android SDK小于9时,基于HttpClient创建HttpStack,否则基于HttpURLConnection创建HttpStack。所以Volley通过策略模式,在SDK不同的版本时选用不同的策略,并且该策略也可以被替换,而不需要修改Volley类的代码。模板方法模式
含义:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法结构即可重定义该算法的某些特定步骤。
适用场景:设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现;需要对代码进行重构,将各个子类公共行为抽取出来集中到一个共同的父类中以避免代码重复。
Volley中的模板方法模式:Volley中对于Request的设计用到的就是模板方法模式。无论是请求Image,String,JsonObject还是JsonArray,唯一的区别就是对返回数据的解析方式(parseNetworkError)不同,如果我们就可以通过模板方法模式对解析方式进行抽象,让子类分别实现,这样如果有新的对象返回需要解析,只要新增子类实现对返回数据的解析方式就可以实现功能拓展。
-
Q: Volley的优点
A:
- 默认采用缓存机制,只有当缓存中不存在相应的请求数据时才进行网络请求,这样减少了应用响应时间,提高了程序性能,非常适合于移动设备上面的网络请求,因为移动设备上的网络请求的特点是单次请求的数据量比较小,但数据请求操作比较频繁,Volley便是为此类网络请求而生;并且默认的缓存实现,将缓存以文件的形式存储在 Disk,程序退出后不会丢失;
- 对缓存文件进行双重校验,先校验是否过期,未过期的前提下,校验是否新鲜,这样既能减少应用程序响应时间,又能确保用户能获得最新数据;(是否过期校验可以确保用户获得是数据是有效数据;先将未过期数据发送给用户,可以减少响应时间;然后校验数据是否新鲜,如果不新鲜,则在下次请求数据时返回最新数据,这很符合用户使用习惯。一般进入一个应用程序后并不一定需要立即刷新数据,而是等到用户刷新时,再提供新数据,这样可以减少应用启动时间,减少不必要的流量消耗);
- 根据Android API而默认使用不同的网络请求方案,API 9以前的应用使用HttpClient,API 9及以后的设备使用HttpUrlConnection,这样既能够避免HttpUrlConnection在API 9以前的弊端(调用close方法会影响连接池,导致连接复用失效),又能够最大限度地提高应用程序性能;
- ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成字节数组的缓存。当需要使内存区域的时候,先从已经分配的区域中获得以减少内存分配次数。当空间用完以后,在将数据返回给此缓冲区。这样,就会减少内存区域堆内存的波动和减少GC的回收,让CPU把更多的性能留给页面的渲染,提高性能。通过这个类发现,谷歌对技术的细节十分考究;
- 谷歌官方提供的网络框架,基本不需要考虑兼容性问题;
- 框架比较轻量级,减少程序内存占用(程序性能的另一方面);
- 架构设计比较优雅,面向接口编程,极大地提高了框架的可扩展性,编程人员可以根据自己应用的特点定制自己的网络请求、响应头、响应数据等;
- 官方文档详细,网络资料也很丰富,源码书写优雅,规范,编程人员学习成本低。