标签: Volley

Volley(一 )—— 框架简介

Volley(一 )—— 框架简介

一、引言

虽然网上已经有很多大神、高手都写过了类似的帖子,但作为新人,必须要走模仿的道路,再考虑超越,因此学习大神的笔记,记录自己的理解,是一个菜鸟走向成功的必经之路啊。如签名所言,记录自己摸爬滚打的经历,享受不悔的青春。废话不多说,言归正传。

二、Volley是什么?

Volley是 Google 推出的 Android 异步网络请求框架和图片加载框架。

三、Volley的主要特性

(1). 提供对网络请求自动调控的管理功能RequestQueue请求队列
(2).提供对网络请求进行缓存的功能,数据可以二级缓存,图片可以三级缓存,缓存有效时间默认使用请求响应头内CacheControl设置的缓存有效时间,也就是缓存有效时间由服务端动态确定
(3). 提供强大的请求取消功能
(4). 提供简便的图片加载工具ImageLoader&NetworkImageView

(5).提供强大的自定制功能,可以自己按照自己的实际需求定制request<T>子类

四、volley的总体设计图

%title插图%num

上图源自网络。如上图所示,volley主要是通过两种Diapatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取 得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。

五、volley中的一些概念

  • Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
  • Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest都是它的子类,表示某种类型的请求。
  • RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、 NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
  • CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求 处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入 NetworkDispatcher去调度处理。
  • NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
  • ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
  • HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
  • Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
  • Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

六、volley请求处理流程图

%title插图%num

上图源自网络。

Android 异步网络请求框架-Volley

Android 异步网络请求框架-Volley
1. 功能介绍
1.1. Volley
Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。
名字由来:a burst or emission of many things or a large amount at once
发布演讲时候的配图
%title插图%num
Volley 的特点:特别适合数据量小,通信频繁的网络操作。(Android 应用中*大多数的网络操作都属于这种类型)。

1.2 Volley 的主要特点
(1). 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
(2). 一定程度符合 Http 规范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的处理,请求头的处理,缓存机制的支持等。并支持重试及优先级定义。
(3). 默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现,这两者的区别及优劣在4.2.1 Volley中具体介绍。
(4). 提供简便的图片加载工具。

2. 总体设计
2.1 总体设计图

%title插图%num

上面是 Volley 的总体设计图,主要是通过两种Dispatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。
2.2 Volley 中的概念
简单介绍一些概念,在详细设计中会仔细介绍。
Volley 的调用比较简单,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue后,只需要往这个RequestQueue不断 add Request 即可。
Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest 都是它的子类,表示某种类型的请求。
RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

3. 流程图
Volley 请求流程图:

%title插图%num
4. 详细设计
4.1 类关系图

%title插图%num

这是 Volley 框架的主要类关系图
图中红色圈内的部分,组成了 Volley 框架的核心,围绕 RequestQueue 类,将各个功能点以组合的方式结合在了一起。各个功能点也都是以接口或者抽象类的形式提供。
红色圈外面的部分,在 Volley 源码中放在了 toolbox 包中,作为 Volley 为各个功能点提供的默认的具体实现。
通过类图我们看出, Volley 有着非常好的拓展性。通过各个功能点的接口,我们可以给出自定义的,更符合我们需求的具体实现。

4.2 核心类功能介绍
4.2.1 Volley.java
这个和 Volley 框架同名的类,其实是个工具类,作用是构建一个可用于添加网络请求的RequestQueue对象。
(1). 主要函数
Volley.java 有两个重载的静态方法。
public static RequestQueue newRequestQueue(Context context)

public static RequestQueue newRequestQueue(Context context, HttpStack stack)
*个方法的实现调用了第二个方法,传 HttpStack 参数为 null。
第二个方法中,如果 HttpStatck 参数为 null,则如果系统在 Gingerbread 及之后(即 API Level >= 9),采用基于 HttpURLConnection 的 HurlStack,如果小于 9,采用基于 HttpClient 的 HttpClientStack。

if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

得到了 HttpStack,然后通过它构造一个代表网络(Network)的具体实现BasicNetwork。
接着构造一个代表缓存(Cache)的基于 Disk 的具体实现DiskBasedCache。
*后将网络(Network)对象和缓存(Cache)对象传入构建一个 RequestQueue,启动这个 RequestQueue,并返回。

Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;

我们平时大多采用Volly.newRequestQueue(context)的默认实现,构建 RequestQueue。
通过源码可以看出,我们可以抛开 Volley 工具类构建自定义的 RequestQueue,采用自定义的HttpStatck,采用自定义的Network实现,采用自定义的 Cache 实现等来构建RequestQueue。

(2). HttpURLConnection 和 AndroidHttpClient(HttpClient 的封装)如何选择及原因:
在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。
另外在 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。
再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。

4.2.2 Request.java
代表一个网络请求的抽象类。我们通过构建一个Request类的非抽象子类(StringRequest、JsonRequest、ImageRequest 或自定义)对象,并将其加入到•RequestQueue•中来完成一次网络请求操作。
Volley 支持 8 种 Http 请求方式 GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
Request 类中包含了请求 url,请求请求方式,请求 Header,请求 Body,请求的优先级等信息。
因为是抽象类,子类必须重写的两个方法。
abstract protected Response parseNetworkResponse(NetworkResponse response);
子类重写此方法,将网络返回的原生字节内容,转换成合适的类型。此方法会在工作线程中被调用。
abstract protected void deliverResponse(T response);
子类重写此方法,将解析成合适类型的内容传递给它们的监听回调。
以下两个方法也经常会被重写

public byte[] getBody()
1
重写此方法,可以构建用于 POST、PUT、PATCH 请求方式的 Body 内容。
protected Map

4.2.3 RequestQueue.java
Volley 框架的核心类,将请求 Request 加入到一个运行的RequestQueue中,来完成请求操作。
(1). 主要成员变量
RequestQueue 中维护了两个基于优先级的 Request 队列,缓存请求队列和网络请求队列。
放在缓存请求队列中的 Request,将通过缓存获取数据;放在网络请求队列中的 Request,将通过网络获取数据。

private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();

维护了一个正在进行中,尚未完成的请求集合。

private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。

private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();

(2). 启动队列
创建出 RequestQueue 以后,调用 start 方法,启动队列。

/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();

// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}

start 方法中,开启一个缓存调度线程CacheDispatcher和 n 个网络调度线程NetworkDispatcher,这里 n 默认为 4,存在优化的余地,比如可以根据 CPU 核数以及网络类型计算更合适的并发数。
缓存调度线程不断的从缓存请求队列中取出 Request 去处理,网络调度线程不断的从网络请求队列中取出 Request 去处理。
(3). 加入请求

public <T> Request<T> add(Request<T> request);

流程图如下:

%title插图%num

(4). 请求完成

void finish(Request<?> request)

Request 请求结束:

(1). 首先从正在进行中请求集合mCurrentRequests中移除该请求。
(2). 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中,让缓存请求处理线程CacheDispatcher自动处理。
(5). 请求取消

public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)

取消当前请求集合中所有符合条件的请求。
filter 参数表示可以按照自定义的过滤器过滤需要取消的请求。
tag 表示按照Request.setTag设置好的 tag 取消请求,比如同属于某个 Activity 的。

4.2.4 CacheDispatcher.java
一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery 去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
(1). 成员变量

BlockingQueue<Request<?>> mCacheQueue 缓存请求队列
BlockingQueue<Request<?>> mNetworkQueue 网络请求队列
Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
ResponseDelivery mDelivery 请求结果传递类

(2). 处理流程图

%title插图%num

4.2.5 NetworkDispatcher.java
一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery 去执行后续处理,并判断结果是否要进行缓存。
(1). 成员变量

BlockingQueue<Request<?>> mQueue 网络请求队列
Network mNetwork 网络类,代表了一个可以执行请求的网络
Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
ResponseDelivery mDelivery 请求结果传递类,可以传递请求的结果或者错误到调用者

(2). 处理流程图

%title插图%num

4.2.6 Cache.java
缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。
(1). 主要方法:

public Entry get(String key); 通过 key 获取请求的缓存实体
public void put(String key, Entry entry); 存入一个请求的缓存实体
public void remove(String key); 移除指定的缓存实体
public void clear(); 清空缓存

(2). 代表缓存实体的内部类 Entry
成员变量和方法

byte[] data 请求返回的数据(Body 实体)
String etag Http 响应首部中用于缓存新鲜度验证的 ETag
long serverDate Http 响应首部中的响应产生时间
long ttl 缓存的过期时间
long softTtl 缓存的新鲜时间
Map<String, String> responseHeaders 响应的 Headers
boolean isExpired() 判断缓存是否过期,过期缓存不能继续使用
boolean refreshNeeded() 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测

4.2.7 DiskBasedCache.java
继承 Cache 类,基于 Disk 的缓存实现类。
(1). 主要方法:

public synchronized void initialize() 初始化,扫描缓存目录得到所有缓存数据摘要信息放入内存。
public synchronized Entry get(String key) 从缓存中得到数据。先从摘要信息中得到摘要信息,然后读取缓存数据文件得到内容。
public synchronized void put(String key, Entry entry) 将数据存入缓存内。先检查缓存是否会满,会则先删除缓存中部分数据,然后再新建缓存文件。
private void pruneIfNeeded(int neededSpace) 检查是否能再分配 neededSpace 字节的空间,如果不能则删除缓存中部分数据。
public synchronized void clear() 清空缓存。 public synchronized void remove(String key) 删除缓存中某个元素。

(2). CacheHeader 类
CacheHeader 是缓存文件摘要信息,存储在缓存文件的头部,与上面的Cache.Entry相似。

4.2.8 NoCache.java
继承 Cache 类,不做任何操作的缓存实现类,可将它作为构建RequestQueue的参数以实现一个不带缓存的请求队列。

4.2.9 Network.java
代表网络的接口,处理网络请求。
唯一的方法,用于执行特定请求。

public NetworkResponse performRequest(Request<?> request) throws VolleyError;

4.2.10 NetworkResponse.java
Network中方法 performRequest 的返回值,Request的 parseNetworkResponse(…) 方法入参,是 Volley 中用于内部 Response 转换的一级。
封装了网络请求响应的 StatusCode,Headers 和 Body 等。
(1). 成员变量

int statusCode Http 响应状态码
byte[] data Body 数据
Map<String, String> headers 响应 Headers
boolean notModified 表示是否为 304 响应
long networkTimeMs 请求耗时

(2). Volley 的内部 Response 转换流程图

%title插图%num

从上到下表示从得到数据后一步步的处理,箭头旁的注释表示该步处理后的实体类。

4.2.11 BasicNetwork.java
实现 Network,Volley 中默认的网络接口实现类。调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
主要实现了以下功能:
(1). 利用 HttpStack 执行网络请求。
(2). 如果 Request 中带有实体信息,如 Etag,Last-Modify 等,则进行缓存新鲜度的验证,并处理 304(Not Modify)响应。
(3). 如果发生超时,认证失败等错误,进行重试操作,直到成功、抛出异常(不满足重试策略等)结束。

4.2.12 HttpStack.java
用于处理 Http 请求,返回请求结果的接口。目前 Volley 中的实现有基于 HttpURLConnection 的 HurlStack 和 基于 Apache HttpClient 的 HttpClientStack。
唯一方法,执行请求

public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;

执行 Request 代表的请求,第二个参数表示发起请求之前,添加额外的请求 Headers。

4.2.13 HttpClientStack.java
实现 HttpStack 接口,利用 Apache 的 HttpClient 进行各种请求方式的请求。
基本就是 org.apache.http 包下面相关类的常见用法,不做详解,不过与下面 HttpURLConnection 做下对比就能发现 HttpURLConnection 的 API 相对简单的多。

4.2.14 HurlStack.java
实现 HttpStack 接口,利用 Java 的 HttpURLConnection 进行各种请求方式的请求。

4.2.15 Response.java
封装了经过解析后的数据,用于传输。并且有两个内部接口 Listener 和 ErrorListener 分别可表示请求失败和成功后的回调。
Response 的构造函数被私有化,而通过两个函数名更易懂的静态方法构建对象。

4.2.16 ByteArrayPool.java
byte[] 的回收池,用于 byte[] 的回收再利用,减少了内存的分配和回收。 主要通过一个元素长度从小到大排序的ArrayList作为 byte[] 的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素。

public synchronized void returnBuf(byte[] buf)

将用过的 byte[] 回收,根据 byte[] 长度按照从小到大的排序将 byte[] 插入到缓存中合适位置。

public synchronized byte[] getBuf(int len)

获取长度不小于 len 的 byte[],遍历缓存,找出*个长度大于传入参数len的 byte[],并返回;如果*终没有合适的 byte[],new 一个返回。

private synchronized void trim()

当缓存的 byte 超过预先设置的大小时,按照先进先出的顺序删除*早的 byte[]。

4.2.17 PoolingByteArrayOutputStream.java
继承 ByteArrayOutputStream,原始 ByteArrayOutputStream 中用于接受写入 bytes 的 buf,每次空间不足时便会 new 更大容量的 byte[],而 PoolingByteArrayOutputStream 使用了 ByteArrayPool 作为 Byte[] 缓存来减少这种操作,从而提高性能。

4.2.18 HttpHeaderParser.java
Http header 的解析工具类,在 Volley 中主要作用是用于解析 Header 从而判断返回结果是否需要缓存,如果需要返回 Header 中相关信息。
有三个方法

public static long parseDateAsEpoch(String dateStr)
解析时间,将 RFC1123 的时间格式,解析成 epoch 时间
public static String parseCharset(Map<String, String> headers)
解析编码集,在 Content-Type 首部中获取编码集,如果没有找到,默认返回 ISO-8859-1
public static Cache.Entry parseCacheHeaders(NetworkResponse response)
比较重要的方法,通过网络响应中的缓存控制 Header 和 Body 内容,构建缓存实体。如果 Header 的 Cache-Control 字段含有no-cache或no-store表示不缓存,返回 null。

(1). 根据 Date 首部,获取响应生成时间
(2). 根据 ETag 首部,获取响应实体标签
(3). 根据 Cache-Control 和 Expires 首部,计算出缓存的过期时间,和缓存的新鲜度时间
两点需要说明下:
1.没有处理Last-Modify首部,而是处理存储了Date首部,并在后续的新鲜度验证时,使用Date来构建If-Modified-Since。 这与 Http 1.1 的语义有些违背。
2.计算过期时间,Cache-Control 首部优先于 Expires 首部。

4.2.19 RetryPolicy.java
重试策略接口
有三个方法:

public int getCurrentTimeout();
获取当前请求用时(用于 Log)
public int getCurrentRetryCount();
获取已经重试的次数(用于 Log)
public void retry(VolleyError error) throws VolleyError;
确定是否重试,参数为这次异常的具体信息。在请求异常时此接口会被调用,可在此函数实现中抛出传入的异常表示停止重试。

4.2.20 DefaultRetryPolicy.java
实现 RetryPolicy,Volley 默认的重试策略实现类。主要通过在 retry(…) 函数中判断重试次数是否达到上限确定是否继续重试。
其中mCurrentRetryCount变量表示已经重试次数。
mBackoffMultiplier表示每次重试之前的 timeout 该乘以的因子。
mCurrentTimeoutMs变量表示当前重试的 timeout 时间,会以mBackoffMultiplier作为因子累计前几次重试的 timeout。

4.2.21 ResponseDelivery.java
请求结果的传输接口,用于传递请求结果或者请求错误。
有三个方法:

public void postResponse(Request<?> request, Response<?> response);
此方法用于传递请求结果,request 和 response 参数分别表示请求信息和返回结果信息。
public void postResponse(Request<?> request, Response<?> response, Runnable runnable);
此方法用于传递请求结果,并在完成传递后执行 Runnable。
public void postError(Request<?> request, VolleyError error);
此方法用于传输请求错误。

4.2.22 ExecutorDelivery.java
请求结果传输接口具体实现类。
在 Handler 对应线程中传输缓存调度线程或者网络调度线程中产生的请求结果或请求错误,会在请求成功的情况下调用 Request.deliverResponse(…) 函数,失败时调用 Request.deliverError(…) 函数。

4.2.23 StringRequest.java
继承 Request 类,代表了一个返回值为 String 的请求。将网络返回的结果数据解析为 String 类型。通过构造函数的 listener 传参,支持请求成功后的 onResponse(…) 回调。

4.2.24 JsonRequest.java
抽象类,继承自 Request,代表了 body 为 JSON 的请求。提供了构建 JSON 请求参数的方法。

4.2.25 JsonObjectRequest.java
继承自 JsonRequest,将网络返回的结果数据解析为 JSONObject 类型。

4.2.26 JsonArrayRequest.java
继承自 JsonRequest,将网络返回的结果数据解析为 JSONArray 类型。

4.2.27 ImageRequest.java
继承 Request 类,代表了一个返回值为 Image 的请求。将网络返回的结果数据解析为 Bitmap 类型。
可以设置图片的*大宽度和*大高度,并计算出合适尺寸返回。每次*多解析一张图片防止 OOM。

4.2.28 ImageLoader.java
封装了 ImageRequst 的方便使用的图片加载工具类。
1.可以设置自定义的ImageCache,可以是内存缓存,也可以是 Disk 缓存,将获取的图片缓存起来,重复利用,减少请求。
2.可以定义图片请求过程中显示的图片和请求失败后显示的图片。
3.相同请求(相同地址,相同大小)只发送一个,可以避免重复请求。
// TODO

4.2.29 NetworkImageView.java
利用 ImageLoader,可以加载网络图片的 ImageView
有三个公开的方法:

public void setDefaultImageResId(int defaultImage)
设置默认图片,加载图片过程中显示。
public void setErrorImageResId(int errorImage)
设置错误图片,加载图片失败后显示。
public void setImageUrl(String url, ImageLoader imageLoader)
设置网络图片的 Url 和 ImageLoader,将利用这个 ImageLoader 去获取网络图片。
如果有新的图片加载请求,会把这个 ImageView 上旧的加载请求取消。

4.2.30 ClearCacheRequest.java
用于人为清空 Http 缓存的请求。
添加到 RequestQueue 后能很快执行,因为优先级很高,为Priority.IMMEDIATE。并且清空缓存的方法mCache.clear()写在了isCanceled()方法体中,能*早的得到执行。
ClearCacheRequest 的写法不敢苟同,目前看来唯一的好处就是可以将清空缓存操作也当做一个请求。而在isCanceled()中做清空操作本身就造成了歧义,不看源码没人知道在NetworkDispatcher run 方法循环的过程中,isCanceled()这个读操作竟然做了可能造成缓存被清空。只能跟源码的解释一样当做一个 Hack 操作。

总结(可以学习的地方):
1. 兼容性好:默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现 (android 2.3以下HttpURLConnection有严重bug)
2. 提供了取消Http 请求的接口,在Activity关闭但是仍旧执行的Http请求可以及时销毁,减少了性能的开销以及Activity能够及时回收。
3. 使用了多线程(默认4个线程)+ 请求队列的形式,而不是每次请求都单独开启一个线程,减少了性能的开销。
4. 支持缓存:在Http协议层(在进行条件请求时,客户端会提供给服务器一个If-Modified-Since请求头,其值为服务器上次返回的Last-Modified响应头中的日期值, 服务器会读取到这两个请求头中的值,判断出客户端缓存的资源是否是*新的,如果是的话,服务器就会返回HTTP/304 Not Modified响应)和客户端都支持cache,大大减少了服务器压力。同时对客户端性能开销也会有一定的减少。
5. 避免同一个http请求多次连接服务器:如果某个请求已经在等待队列,并且该请求允许缓存,将该请求放到等待队列中,使得URI一致的请求不会重复去请求服务器.
6. 从框架本身的角度来说,Request接口、Cache接口、HttpStack等都是以接口的形式来定义的,充分地展示了Java中面向接口编程的特点,使得代码的扩展性大大增强

5. 注意事项
Volley 适合于数据量小的并发请求,因为在数据请求完成后,会将请求得到的数据读到内存中存放在byte[],然后分发给调用者进行数据的转换。若用volley请求大数据则可能会OOM。
Volley的线程池不具备伸缩功能,创建后所有线程均处于启用状态,不支持动态调整。
————————————————

Volley缓存说明 — 一个请求两次回调

从上一篇文章Android 异步网络请求框架-Volley 了解volley的一些出来过程,当然也包含网络请求和缓存处理的流程,但是在此需要单独做一些说明。
我在使用过程中忽略了一个事情,就是一个网络请求可能会有两次结果回调,其实这个在上一篇文章中也有说明,只是没有特别说明,很容易被忽略。

这里写图片描述
当进行网络请求时,先会判断缓存,当缓存还未过期,但是需要刷新时,volley会先将缓存回调(*次),然后以*高优先级再次发起一个网络请求,若请求到的内容有变化时,会再次回调(第二次)。当然,如果请求到的内容没有变化(the server returned 304),则不会进行第二次回调。因为*次将缓存回调时已经将请求标记为回调过的:

// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
    request.finish("not-modified");
    continue;
}

当然,还有一点需要注明一下,如果缓存已经过期了,是不会进行回调的,也就是说过期的缓存等同于缓存不存在。但是,volley在初始化缓存是并没有对过期的缓存进行处理,个人觉得这里可以进行优化:
在初始化时,将已过期的缓存删除,好处:
1. 在请求时减小过期缓存的命中率(区别于在初始化之后才过期的缓存);
2. 减小不必要的内存和磁盘占用;
3. 提高缓存的使用率(在进行Lru删除缓存时,可能会清除了有用的缓存而保留了过期的缓存);

Android中关于Volley的使用 从RequestQueue开始来深入认识Volley

在前面的几篇文章中,我们学习了如何用Volley去网络加载JSON数据,如何利用ImageRequest和NetworkImageView去网络加载数据,而关于Volley的使用,我们都是从下面一行代码开始的:

 

  1. Volley.newRequestQueue(this);  

这是Volley类创建了一个RequestQueue,而关于Volley的一切就是从这个时候开始的,我们就深入地学习一下在这个方法后面到底有着什么样的实现吧。

 

我们来看看Volley类的实现:

 

  1. public class Volley {  
  2.     …
  3.     public static RequestQueue newRequestQueue(Context context, HttpStack stack) {  
  4.         …
  5.     }
  6.     /** 
  7.      * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 
  8.      * 
  9.      * @param context A {@link Context} to use for creating the cache dir. 
  10.      * @return A started {@link RequestQueue} instance. 
  11.      */  
  12.     public static RequestQueue newRequestQueue(Context context) {  
  13.         return newRequestQueue(context, null);  
  14.     }
  15. }

Volley类只有两个方法,而主要的创建RequestQueue的方法就是包含两个参数Context和HttpStack的newRequestQueue方法了,另外一个只是调用这个方法,将传一个null的HttpStack而已。

 

我们看看这个方法里面的实现:

 

  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {  
  2.     File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);//缓存文件  
  3.     String userAgent = “volley/0”;//UserAgent用来封装应用的包名跟版本号,提供给服务器,就跟浏览器信息一样  
  4.     try {  
  5.         String packageName = context.getPackageName();
  6.         PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);  
  7.         userAgent = packageName + “/” + info.versionCode;  
  8.     } catch (NameNotFoundException e) {  
  9.     }
  10.     if (stack == null) {//一般我们都不需要传这个参数进来,而volley则在这里会根据SDK的版本号来判断   
  11.         if (Build.VERSION.SDK_INT >= 9) {  
  12.             stack = new HurlStack();//SDK如果大于等于9,也就是Android 2.3以后,因为引进了HttpUrlConnection,所以会用一个HurlStack  
  13.         } else {//如果小于9,则是用HttpClient来实现  
  14.             // Prior to Gingerbread, HttpUrlConnection was unreliable.  
  15.             // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html  
  16.             stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));  
  17.         }
  18.     }
  19.     Network network = new BasicNetwork(stack);//创建一个Network,构造函数需要一个stack参数,Network里面会调用stack去跟网络通信  
  20. n style=“white-space:pre”>  </span>//创建RequestQueue,并将缓存实现DiskBasedCache和网络实现BasicNetwork传进去,然后调用start方法  
  21.     RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  
  22.     queue.start();
  23.     return queue;  
  24. }

大家可以看代码中的注释,这里简要说明一下步骤:

 

1)创建缓存文件和UserAgenp字符串

2)根据SDK版本来创建HttpStack的实现,如果是2.3以上的,则使用基于HttpUrlConnection实现的HurlStack,反之,则利用HttpClient实现的HttpClientStack。

3)创建一个BasicNetwork对象,并将HttpStack封装在Network中

4)创建一个DiskBasedCache对象,和Network一起,传给RequestQueue作为参数,创建RequestQueue对象。

5)调用 RequestQueue的 start 方法,然后返回创建的queue对象。

接下来,我们看看RequestQueue的构造函数:

 

  1. public RequestQueue(Cache cache, Network network) {  
  2.     this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);//跟网络交互的线程数量,默认是4  
  3. }

 

很明显,调用了另外一个构造函数:

  1. public RequestQueue(Cache cache, Network network, int threadPoolSize) {  
  2.       this(cache, network, threadPoolSize,  
  3.               new ExecutorDelivery(new Handler(Looper.getMainLooper())));  
  4.   }

而*终会调用到下面这个构造函数来创建对象:

  1. public RequestQueue(Cache cache, Network network, int threadPoolSize,  
  2.         ResponseDelivery delivery) {
  3.     mCache = cache;//缓存  
  4.     mNetwork = network;//网络  
  5.     mDispatchers = new NetworkDispatcher[threadPoolSize];//线程池  
  6.     mDelivery = delivery;//派送Response的实现  
  7. }

在构造函数中,我们可以看到在Volley类中创建的Cache和Network。

 

另外,通过前面传进来的线程数量(默认是4),会创建一个NetworkDispatcher的数组,也就是创建了一个有4个线程的线程池,因为NetworkDispatcher是继承于Thread的实现类,其定义如下:

 

  1. public class NetworkDispatcher extends Thread {  

而delivery的实现则是ExecutorDelivery,我们可以看到它的参数是一个Handler,而Handler的构造函数参 数则是Looper.getMainLooper(),这其实是应用的主线程的Looper,也就是说,Handler其实是主线程中的 Hanlder,ExecutorDelivery的定义如下:

 

  1. public ExecutorDelivery(final Handler handler) {  
  2.     // Make an Executor that just wraps the handler.  
  3.     mResponsePoster = new Executor() {  
  4.         @Override  
  5.         public void execute(Runnable command) {  
  6.             handler.post(command);
  7.         }
  8.     };
  9. }

主要作用也就是利用Handler来将Response传回主线程进行UI更新,比如之前的更新ImageView,因为我们知道,UI的更新必须在主线程。

 

到这里,我们的RequestQueue对象就创建好了,下面就是要调用它的start方法了。

 

  1. public void start() {  
  2.     stop();  // 保证所有正在运行的Dispatcher(也就是线程)都停止  
  3.     // 创建缓存的派发器(也是一个线程),并启动线程。  
  4.     mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);  
  5.     mCacheDispatcher.start();
  6.     // 根据线程池的大小,创建相对应的NetworkDispatcher(线程),并启动所有的线程。  
  7.     for (int i = 0; i < mDispatchers.length; i++) {  
  8.         NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,  
  9.                 mCache, mDelivery);
  10.         mDispatchers[i] = networkDispatcher;
  11.         networkDispatcher.start();
  12.     }
  13. }
  14. /** 
  15.  * 停止缓存线程跟所有的网络线程 
  16.  */  
  17. public void stop() {  
  18.     if (mCacheDispatcher != null) {  
  19.         mCacheDispatcher.quit();
  20.     }
  21.     for (int i = 0; i < mDispatchers.length; i++) {  
  22.         if (mDispatchers[i] != null) {  
  23.             mDispatchers[i].quit();
  24.         }
  25.     }
  26. }

我们可以看到,

 

1)start方法的一开始,会先调用stop方法。stop会将缓存线程还有所有的网络线程停止。

2)重新创建一个缓存线程,并启动,在这里,会将 mCacheQueue,mNetwrok, mCache 和 mDelivery 传给其构造函数。

3)根据线程池的大小,创建相对应数目的网络线程,而在这里,我们可以看到会将 mNetworkQueue,mNetwrok,mCache 和 mDelivery作为参数传给NetworkDispatcher。

很明显,当调用RequestQueue的 start方法的时候,其实也就是启动了一个缓存线程和默认的4个网络线程,它们就会在后面静静地等待请求的到来。

在两个构造函数上面,mNetwork, mCache 和 mDelivery,我们上面都介绍过了,但是 mCacheQueue 和 mNetworkQueue,这两个具体是什么样的呢?

 

  1. private final PriorityBlockingQueue<Request<?>> mCacheQueue =  
  2.     new PriorityBlockingQueue<Request<?>>();  
  3. /** The queue of requests that are actually going out to the network. */  
  4. private final PriorityBlockingQueue<Request<?>> mNetworkQueue =  
  5.     new PriorityBlockingQueue<Request<?>>();  

我们可以看到它们其实都是Java并发(Concurrent)包中提供的利用优先级来执行的阻塞队列 PriorityBlockingQueue。显然,它们就应该是来放置从外面传进来的请求的,比如JsonRequest,ImageRequest和 StringRequest。

 

而RequestQueue类中,还有另外两个请求集合:

 

  1. //等待中的请求集合  
  2. private final Map<String, Queue<Request<?>>> mWaitingRequests =  
  3.         new HashMap<String, Queue<Request<?>>>();  
  4. //所有在队列中,或者正在被处理的请求都会在这个集合中  
  5. private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();  

 

我们记得,当我们创建好RequestQueue对象之后,如果我们想要去加载图片,我们就会创建ImageRequest对象,如果我们想要去获 取Json数据,我们就会创建JsonRequest对象,而*后我们都会调用 RequestQueue的add方法,来将请求加入到队列中的。

  1. public <T> Request<T> add(Request<T> request) {  
  2.     // 将请求的队列设置为当前队列,并将请求添加到mCurrentRequests中,表明是正在处理中的,而在这里,我们可以看到利用synchronized来同步  
  3.     request.setRequestQueue(this);  
  4.     synchronized (mCurrentRequests) {  
  5.         mCurrentRequests.add(request);
  6.     }
  7.     // 在这里会设置序列号,保证每个请求都是按顺序被处理的。  
  8.     request.setSequence(getSequenceNumber());
  9.     request.addMarker(“add-to-queue”);  
  10.     // 如果这个请求是设置不缓存的,那么就会将其添加到mNetworkQueue中,直接去网络中获取数据  
  11.     if (!request.shouldCache()) {  
  12.         mNetworkQueue.add(request);
  13.         return request;  
  14.     }
  15.     // 到这里,表明这个请求可以去先去缓存中获取数据。  
  16.     synchronized (mWaitingRequests) {  
  17.         String cacheKey = request.getCacheKey();
  18.         if (mWaitingRequests.containsKey(cacheKey)) {//<span style=”font- family: Arial, Helvetica, sans-serif; font-size: 12px;”>如果这个请求已经有一个相同 的请求(相同的CacheKey)在mWatingRequest中,那么就要将相同CacheKey的请求用一个LinkedList给装起来,先不需 要处理,等那个正在处理的请求结束后,再看看应该怎么处理。</span>  
  19.             // There is already a request in flight. Queue up.  
  20.             Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
  21.             if (stagedRequests == null) {  
  22.                 stagedRequests = new LinkedList<Request<?>>();  
  23.             }
  24.             stagedRequests.add(request);
  25.             mWaitingRequests.put(cacheKey, stagedRequests);
  26.             if (VolleyLog.DEBUG) {  
  27.                 VolleyLog.v(“Request for cacheKey=%s is in flight, putting on hold.”, cacheKey);  
  28.             }
  29.         } else {  
  30. n style=“white-space:pre”>      </span>//如果mWaitingRequest中没有,那么就将其添加到集合中,将添加到mCacheQueue队列中,表明现在这个cacheKey的请求已经在处理了。  
  31.             mWaitingRequests.put(cacheKey, null);  
  32.             mCacheQueue.add(request);
  33.         }
  34.         return request;  
  35.     }
  36. }

而当mCacheQueue或者mNetworkQueue利用add方法添加请求之后,在运行的线程就会接收到请求,从而去处理相对应的请求,*后将处理的结果由mDelivery来发送到主线程进行更新。

 

到这里,我们的请求就会在缓存线程或者网络线程中去处理了,当它们结束之后,每一个Request就会调用自身的finish方法,如下:

 

  1. void finish(final String tag) {  
  2.     if (mRequestQueue != null) {  
  3.         mRequestQueue.finish(this);  
  4.     }

而在这里,它调用的其实是 RequestQueue的finish方法,如下:

 

  1. void finish(Request<?> request) {  
  2.     // Remove from the set of requests currently being processed.  
  3.     synchronized (mCurrentRequests) {  
  4.         mCurrentRequests.remove(request);
  5.     }
  6.     if (request.shouldCache()) {  
  7.         synchronized (mWaitingRequests) {  
  8.             String cacheKey = request.getCacheKey();
  9.             Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
  10.             if (waitingRequests != null) {  
  11.                 if (VolleyLog.DEBUG) {  
  12.                     VolleyLog.v(“Releasing %d waiting requests for cacheKey=%s.”,  
  13.                             waitingRequests.size(), cacheKey);
  14.                 }
  15.                 // Process all queued up requests. They won’t be considered as in flight, but  
  16.                 // that’s not a problem as the cache has been primed by ‘request’.  
  17.                 mCacheQueue.addAll(waitingRequests);
  18.             }
  19.         }
  20.     }
  21. }

可以看到,*步就是将请求从mCurrentRequests中移除,正好对应了上面add方法中的添加。

 

第二步就是判断这个请求有没有缓存,如果有,那么我们这个时候,将前面mWaitingQueue中相同CacheKey的一大批请求再一股脑儿的 扔到mCacheQueue中,为什么现在才扔呢?因为前面我们不知道相同CacheKey的那个请求到底在缓存中有没有,如果没有,它需要去网络中获 取,那就等到它从网络中获取之后,放到缓存中后,它结束了,并且已经缓存了,这个时候,我们就可以保证后面那堆相同CacheKey的请求可以在缓存中去 取到数据了,而不需要再去网络中获取了。

在RequestQueue中,还提供了两个方法去取消请求,如下:

 

  1. public void cancelAll(RequestFilter filter) {  
  2.     synchronized (mCurrentRequests) {  
  3.         for (Request<?> request : mCurrentRequests) {  
  4.             if (filter.apply(request)) {  
  5.                 request.cancel();
  6.             }
  7.         }
  8.     }
  9. }
  10. /** 
  11.  * Cancels all requests in this queue with the given tag. Tag must be non-null 
  12.  * and equality is by identity. 
  13.  */  
  14. public void cancelAll(final Object tag) {  
  15.     if (tag == null) {  
  16.         throw new IllegalArgumentException(“Cannot cancelAll with a null tag”);  
  17.     }
  18.     cancelAll(new RequestFilter() {  
  19.         @Override  
  20.         public boolean apply(Request<?> request) {  
  21.             return request.getTag() == tag;  
  22.         }
  23.     });
  24. }

如上,*个cancleAll会获取一个RequestFilter,这是RequestQueue的内部接口,定义如下:

 

  1. public interface RequestFilter {  
  2.     public boolean apply(Request<?> request);  
  3. }

我们需要自己去实现,什么样的请求才是符合我们的过滤器的,然后在cancel中根据我们定义的过滤规则去批量地取消请求。

 

而第二个则是利用创建Request时设置的Tag值,实现RequestFilter,然后调用上一个cancelAll方法,来取消一批同个Tag值的请求。

这两个方法(其实是一种,主要是利用Tag来批量取消请求)跟我们这个流程的关系不大,所以就不在这里多讲了。

嗯,关于RequestQueue中一切,到这里,也就结束了,不知道讲得清不清楚,还希望大家多给点建议。

Volley — 从源码带看Volley的缓存机制

磁盘缓存DiskBasedCache

如果你还不知道volley有磁盘缓存的话,请看一下文章

DiskBasedCache内部结构

它由两部分组成,一部分是头部,一部分是内容;先得从它的内部静态类CacheHeader(缓存的头部信息)讲起,先看它的内部结构:

复制代码
static class CacheHeader {
    /** 缓存文件的大小 */
    public long size;

    /** 缓存文件的唯一标识 */
    public String key;

    /** 这个是与与http请求缓存相关的标签 */
    public String etag;

    /** 服务器的返回来数据的时间 */
    public long serverDate;

    /** TTL 缓存过期时间. */
    public long ttl;

    /** Soft TTL 缓存新鲜度时间. */
    public long softTtl;

    /** 服务器还回来的头部信息. */
    public Map<String, String> responseHeaders;
}

 //可以看到,头部类里包含的都是一些基本信息。再来看一下内容部分,父类Cache里面的的Entry:
 public static class Entry {
    /** 服务端返回数据的主要内容. */
    public byte[] data;

    public String etag;

    public long serverDate;

    public long ttl;

    public long softTtl;
}
复制代码

 

 

可以看到,Entry里面和CacheHeader里有四个参数是一样的,只是Entry里多了data[],data[]就是用来保存主要数据 的。看到这你可以有点迷糊,Entry和CacheHeader里为什么要有四个参数一样,先简单说一下原因:volley框架里都用到接口编程,所以实 际代码中除了初始化,你只看到cache,而DiskBasedCache是看不到的,所以必须在Entry里先把那些缓存需要用到的参数保留起来,然后 具体实现和封装放在DiskBasedCache里。

DiskBasedCache的使用流程

  • 初始化

    DiskBasedCache的初始化时在RequestQueue新建时就发生的,可以看Volley.newRequestQueue()的源码:

复制代码
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    ....

    //maxDiskCacheBytes为缓存的*大容量,不传就默认为5M
    if (maxDiskCacheBytes <= -1)
    {
        // No maximum size specified
        queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    }
    else
    {
        // Disk cache size specified
        queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
    }

    queue.start();
    return queue;
}
复制代码

 

可以看到,磁盘缓存的路径为:context.getCacheDir(),如果maxDiskCacheBytes有传入,就以传入的为准,如果为空:

/** 默认的磁盘存放的*大byte */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
...
public DiskBasedCache(File rootDirectory) {
    this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}

 

 

磁盘默认为5M。所以如果你想设置*大的磁盘缓存值,那么就不能直接向下面那样这样初始化了:

    queue = Volley.newRequestQueue(context);

而是需要这样:

    queue = Volley.newRequestQueue(context, 10 * 1024 * 1024);
  • 存放缓存数据

    *次缓存的数据是从哪来的呢,当然是从网上来,看NetWorkDispatcher的run方法里:

复制代码
@Override
public void run() {
    while (true) {
        ...
        try {
            ....
            请求解析http的返回信息
            ....
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            ....
    }
}
复制代码

 

 

其中request.getCacheKey()默认为请求的url,response.cacheEntry是Cache.Entry,里面已存 放好解析完的httpResponse数据,request.shouldCache()默认是需要缓存,如果不需要可调用 request.setShouldCache(false)来去掉缓存功能。

我们把请求和处理的http的返回略过,留下几行关键代码,如果这个请求需要缓存(默认需要)和缓存信息不为空,那么就保存缓存信息。接下来看,DiskBaseCache是怎么保存缓存的:

复制代码
 /**
 * 把缓存数据Entry写进磁盘里
 */
@Override
public synchronized void put(String key, Entry entry) {
    //判断是否有足够的缓存空间来缓存新的数据
    pruneIfNeeded(entry.data.length);

    File file = getFileForKey(key);
    try {
        FileOutputStream fos = new FileOutputStream(file);
        //用enry里面的数据,再封装成一个CacheHeader
        CacheHeader e = new CacheHeader(key, entry);
        //先写头部缓存信息
        boolean success = e.writeHeader(fos);
        if (!success) {
            fos.close();
            VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
            throw new IOException();
        }
        //成功后再写缓存内容
        fos.write(entry.data);
        fos.close();
        //把头部信息先暂时保存在一个容器里
        putEntry(key, e);
        return;
    } catch (IOException e) {
    }
    boolean deleted = file.delete();
    if (!deleted) {
        VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
    }
}
复制代码

 

可以看到,每次写入缓存之前,都先调用pruneIfNeeded()检查对象的大小,当缓冲区空间足够新对象的加入时就直接添加进来,否则会删除 部分对象,一直到新对象添加进来后还会有10%的空间剩余时为止,文件引用以LinkHashMap保存。添加时,首先以URL为key,经过个文本转换 后,以转换后的文本为名称,获取一个file对象。首先向这个对象写入缓存的头文件,然后是真正有用的网络返回数据。*后是当前内存占有量数值的更新,这 里需要注意的是真实数据被写入磁盘文件后,在内存中维护的应用,存的只是数据的相关属性。

  • 从缓存数据里取缓存

我们知道队列创建后就会有一个缓存线程在后台一直运行等待着缓存请求进来,但在等待线程前,会先调用mCache.initialize(),把缓存数据的头部信息放进一个Map类型mEntries里,这样以后要用到就先用mEntries判断,速度更快。

如果请求进来即调用Cache.Entry entry = mCache.get(request.getCacheKey()),那我们就看DiskBaseCache。get方法里做了什么:

复制代码
 @Override
public synchronized Entry get(String key) {
    CacheHeader entry = mEntries.get(key);
    // 如果entry不为空,就直接返回
    if (entry == null) {
        return null;
    }

    File file = getFileForKey(key);
    CountingInputStream cis = null;
    try {
        cis = new CountingInputStream(new FileInputStream(file));
        CacheHeader.readHeader(cis); // eat header
        byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
        return entry.toCacheEntry(data);
    } catch (IOException e) {
        VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
        remove(key);
        return null;
    } finally {
        if (cis != null) {
            try {
                cis.close();
            } catch (IOException ioe) {
                return null;
            }
        }
    }
}
复制代码

 

从方法里可以看到,先从文件里获得字节数输入流,从中减去头部文件的字节数,*后把真正内容的data[]数据拿到再组装成一个Cache.Entry返回。不得不说,Volley这真是精打细算啊。

从上面的分析可见,cache在做一些基础判断时都会先用到缓存的头部数据,如果确定头部信息没问题了,再真正读写内容,原因是头部数据比较小,放在内存中也不占地方,但处理速度会快很多。而真正的数据内容,可能会比较大,处理的开销也大,只在真正需要的地方读写。

Volley对304的处理

http的304状态码的含义是:

如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和*次请求时类似。从而 保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到*新的资源。

完整的过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。(Last-Modified为标记此文件在服务期端*后被修改的时间,ETag是这个请求的token)
  3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
  5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返 回响应304和一个空的响应体。

介绍完304,我们接下来来看看volley是怎么运用304来重用缓存的。

Volley对于头部的解析

首先我们来看一下对于response.header的处理,在每一个request里,都必须继承 parseNetworkResponse(NetworkResponse response)方法,然后在里面用 HttpHeaderParser.parseCacheHeaders()解析类来解析头部数据,具体如下:

复制代码
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
    long now = System.currentTimeMillis();

    Map<String, String> headers = response.headers;

    long serverDate = 0;
    long serverExpires = 0;
    long softExpire = 0;
    long maxAge = 0;
    boolean hasCacheControl = false;

    String serverEtag = null;
    String headerValue;

    headerValue = headers.get("Date");
    if (headerValue != null) {
        serverDate = parseDateAsEpoch(headerValue);
    }

    headerValue = headers.get("Cache-Control");
    if (headerValue != null) {
        hasCacheControl = true;
        String[] tokens = headerValue.split(",");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i].trim();
            //如果Cache-Control里为no-cache和no-store则表示不需要缓存,返回null
            if (token.equals("no-cache") || token.equals("no-store")) {
                return null;
            } else if (token.startsWith("max-age=")) {
                try {
                    maxAge = Long.parseLong(token.substring(8));
                } catch (Exception e) {
                }
            } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                maxAge = 0;
            }
        }
    }

    headerValue = headers.get("Expires");
    if (headerValue != null) {
        serverExpires = parseDateAsEpoch(headerValue);
    }
    serverEtag = headers.get("ETag");
    // Cache-Control takes precedence over an Expires header, even if both exist and Expires
    // is more restrictive.
    if (hasCacheControl) {
        softExpire = now + maxAge * 1000;
    } else if (serverDate > 0 && serverExpires >= serverDate) {
        // Default semantic for Expire header in HTTP specification is softExpire.
        softExpire = now + (serverExpires - serverDate);
    }

    Cache.Entry entry = new Cache.Entry();
    entry.data = response.data;
    entry.etag = serverEtag;
    entry.softTtl = softExpire;
    entry.ttl = entry.softTtl;
    entry.serverDate = serverDate;
    entry.responseHeaders = headers;
    return entry;
}
复制代码

从上面代码可以看出缓存头部是根据 Cache-Control 和 Expires 首部,计算出缓存的过期时间(ttl),和缓存的新鲜度时间(softTtl,默认softTtl和ttl相同),如果有Cache-Control标签 以它为准,没有就以Expires标签里的内容为准。

需要注意的是:Volley没有处理Last-Modify首部,而是处理存储了Date首部,并在后续的新鲜度验证时,使用Date来构建If-Modified-Since。 这与 Http 1.1 的语义有些违背。

Volley对于新鲜度和过期的验证

在使用缓存数据前,Volley会先对验证缓存数据是否过期,是否需要更新等属性,然后一一处理,代码在CacheDispatcher的run方法里:

复制代码
 @Override
public void run() {
    if (DEBUG) VolleyLog.v("start new dispatcher");
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    // 初始化缓存,里面会先把磁盘缓存里的头部数据缓存进内存里,增加处理速度
    mCache.initialize();

    while (true) {
        try {
            // 阻塞线程直到有请求加入,才开始运行
            final Request request = mCacheQueue.take();
            request.addMarker("cache-queue-take");

            //请求是否取消
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }

            // 得到缓存数据entry
            Cache.Entry entry = mCache.get(request.getCacheKey());
            //如果缓存不存在,就把请求交给网络队列取处理
            if (entry == null) {
                request.addMarker("cache-miss");
                mNetworkQueue.put(request);
                continue;
            }

            // 如果请求过期,也需要到网络重新获取数据
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // 到这里就表明缓存数据是可用的,解析缓存
            request.addMarker("cache-hit");
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");

            //验证缓存的新鲜度
            if (!entry.refreshNeeded()) {
               //新鲜的
                mDelivery.postResponse(request, response);
            } else {
                // 不新鲜,虽然把缓存数据分发出去,但还是需要到网络上验证缓存是否需要更新
                request.addMarker("cache-hit-refresh-needed");
                //请求带上缓存属性
                request.setCacheEntry(entry);

                response.intermediate = true;

                // 分发完缓存数据后,将请求加入网络请求队列,判断是否需要更新缓存数据
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(request);
                        } catch (InterruptedException e) {
                            // Not much we can do about this.
                        }
                    }
                });
            }

        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
            continue;
        }
    }
}
复制代码

上面代码都已经加了注释,相信不难理解,那我们继续看,网络请求是怎么判断是否需要更新缓存的,在BasicNetwork.performRequest()里:

复制代码
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
....
while (true) {
    ...
    try {
        Map<String, String> headers = new HashMap<String, String>();
        //如果请求有带属性,就将etag和If-Modified-Since属性加上
        addCacheHeaders(headers, request.getCacheEntry());
        httpResponse = mHttpStack.performRequest(request, headers);
        StatusLine statusLine = httpResponse.getStatusLine();
        int statusCode = statusLine.getStatusCode();

        responseHeaders = convertHeaders(httpResponse.getAllHeaders());
        //如果304就直接用缓存数据返回
        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                    request.getCacheEntry().data, responseHeaders, true);
        }
        .....
        return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
    } catch (SocketTimeoutException e) {
      ...
    }
}
}
复制代码

 

从上面的注释可以看到,如果是返回304就直接用缓存数据返回。那来看NetworkDispatcher的run()里:

复制代码
public void run() {
    ...
    NetworkResponse networkResponse = mNetwork.performRequest(request);
    request.addMarker("network-http-complete");
    // 如果是304并且已经将缓存分发出去里,就直接结束这个请求
    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
        request.finish("not-modified");
        continue;
    }
    ...
    }
}
复制代码

现在流程比较清晰了,在有缓存的情况下,如果已经过期,但是返回304,就复用缓存。如果不新鲜了,就先将缓存分发出去,然后再进行网络请求,看是否需要更新缓存。

不过眼尖的读者一定有个疑惑,在解析头部数据时,默认不是新鲜度和过期事件是一样的吗?那新鲜度不是一定运行不到吗?确实是这样,我也有这个疑惑, 网上也找不到确切的资料来解释这一点。不过按照正常的逻辑,新鲜度时间一定比过期时间短,这样我们就可以根据实际需要更改Volley的源码。例如,我们 可以直接把新鲜度的验证时间设为3分钟,而过期时间设为一天,代码如下:

复制代码
public static Cache.Entry parseIgnoreCacheHeaders(NetworkResponse response) {
    long now = System.currentTimeMillis();
    Map<String, String> headers = response.headers;
    long serverDate = 0;
    String serverEtag = null;
    String headerValue;
    headerValue = headers.get("Date");

    if (headerValue != null) {
       serverDate = HttpHeaderParser.parseDateAsEpoch(headerValue);
    }

    serverEtag = headers.get("ETag");
    final long cacheHitButRefreshed = 3 * 60 * 1000; 
    final long cacheExpired = 24 * 60 * 60 * 1000; 
    final long softExpire = now + cacheHitButRefreshed;
    final long ttl = now + cacheExpired;

    Cache.Entry entry = new Cache.Entry();
    entry.data = response.data;
    entry.etag = serverEtag;
    entry.softTtl = softExpire;
    entry.ttl = ttl;
    entry.serverDate = serverDate;
    entry.responseHeaders = headers;
    return entry;
}
复制代码

然后使用的时候:

复制代码
public class MyRequest extends com.android.volley.Request<MyResponse> {
    ...
    @Override
     protected Response<MyResponse> parseNetworkResponse(NetworkResponse response) {
         String jsonString = new String(response.data);
         MyResponse MyResponse = gson.fromJson(jsonString, MyResponse.class);
         return Response.success(MyResponse, HttpHeaderParser.parseIgnoreCacheHeaders(response));
     }
}
复制代码

这样的话,在3分钟后就不新鲜,24小时后就会过期。

图片的自定义内存缓存

我们使用ImageLoader时会传入一个ImageCache,它是个接口,里面定义了两个方法:

public interface ImageCache {
        public Bitmap getBitmap(String url);
        public void putBitmap(String url, Bitmap bitmap);
}

那他们是什么时候使用的呢,可以从开始请求数据ImageLoader.get()方法看起:

复制代码
 public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        //请求只能在主线程里,不然会报错
        throwIfNotOnMainThread();
        //用url和宽高组成key
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

        //从内存缓存里获取数据
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 如果内存不为空,直接返回图片信息
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        ...
        // 如果为空,就正常请求网络数据,下面用的是ImageRequest取请求网络数据
        Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {
                   //请求成功后,在这个方法里,把图片放进内存缓存中
                    onGetImageSuccess(cacheKey, response);
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    onGetImageError(cacheKey, error);
                }
            });
     ...
    }

    private void onGetImageSuccess(String cacheKey, Bitmap response) {
        //把图片放进内存里
        mCache.putBitmap(cacheKey, response);
        ...
    }
复制代码

从上面的代码注释中已经能比较清晰的看出,每次调用ImageLoader.get()方法,会先从内存缓存里先看有没有数据,有就直接返回,没有 就走正常的网络流程,先查看磁盘缓存,不存在或过期再去请求网络。图片比普通数据多一层缓存的原因也很简单,因为图片较大,读取和网络成本都大,能用缓存 就用缓存,能省一点是一点。

下面来看看具体的流程图
技术分享

以上就是Volley框架所使用到的所有缓存机制,如有遗漏请留言指出,多谢阅读。

Volley — 自定义Request

Volley中提供了几个Request,如果我们有特殊的需求,完全可以自定义Request的,自定义Request自然要继承Request,那么本篇就教大家来一步一步地定义一个自己的Request类。

 

一、继承Request

如果我们的request的对象不是string,也不是JsonObject,而是一个奇怪的对象呢?我这里建立了一个类,叫做:Kale,然后定义了一个CustomReqeust去继承Reqeust,得到如下的代码。

复制代码
复制代码
复制代码
package com.kale.volleytest;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;

public class CustomReqeust extends Request<Kale>{
    
    public CustomReqeust(int method, String url, ErrorListener listener) {
        super(method, url, listener);
    }

    @Override
    protected Response<Kale> parseNetworkResponse(NetworkResponse response) {
        // TODO 自动生成的方法存根
        return null;
    }

    @Override
    protected void deliverResponse(Kale response) {
        // TODO 自动生成的方法存根
        
    }

}
复制代码
复制代码
复制代码

分析:

public CustomReqeust(int method, String url, ErrorListener listener)

构造函数中调用了父类的方法,初始化了当前对象。传入三个参数:①请求方式,即POST/GET,②请求的URL,③出错时的回调监听器

 

protected Response<Kale> parseNetworkResponse(NetworkResponse response)

解析网络响应的结果,从NetworkResponse的代码中我们就可以知道它里面有什么东西了。

复制代码
复制代码
复制代码
/**
 * Data and headers returned from {@link Network#performRequest(Request)}.
 */
public class NetworkResponse {
    /**
     * Creates a new network response.
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @param notModified True if the server returned a 304 and the data was already in cache
     * @param networkTimeMs Round-trip network time to receive network response
     */
    public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
            boolean notModified, long networkTimeMs)
复制代码
复制代码
复制代码

响应码啊,请求头什么的,**主要的就是这个比特数组的data,响应的结果就在里面。我们可以自由的进行处理了~

 

protected void deliverResponse(Kale response)

分发响应的结果,我们可以通过将这个response放到监听器里来获取响应结果。

 

二、分析StringRequest

我们现在已经对request的子类有了基本的认识,现在就来看看StringRequest的源码吧,别担心,很短!

复制代码
复制代码
复制代码
package com.android.volley.toolbox;public class StringRequest extends Request<String> {
    // 建立监听器来获得响应成功时返回的结果
    private final Listener<String> mListener; 


    // 传入请求方法,url,成功时的监听器,失败时的监听器
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        // 初始化成功时的监听器
        mListener = listener;
    }

    /**
     * Creates a new GET request.
     * 建立一个默认的GET请求,调用了上面的构造函数
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        // 用监听器的方法来传递下响应的结果
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 调用了new String(byte[] data, String charsetName) 这个构造函数来构建String对象,
            // 将byte数组按照特定的编码方式转换为String对象
            // 主要部分是data
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}
复制代码
复制代码
复制代码

分析完代码我们应该就能知道改如何自定义Request了,其实没啥高深的东西。

 

三、自定义XMLRequest

代码来自:http://blog.csdn.net/guolin_blog/article/details/17612763

复制代码
复制代码
复制代码
public class XMLRequest extends Request<XmlPullParser> {  
  
    private final Listener<XmlPullParser> mListener;  
  
    public XMLRequest(int method, String url, Listener<XmlPullParser> listener,  
            ErrorListener errorListener) {  
        super(method, url, errorListener);  
        mListener = listener;  
    }  
  
    public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {  
        this(Method.GET, url, listener, errorListener);  
    }  
  
    @Override  
    protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {  
        try {  
            String xmlString = new String(response.data,  
                    HttpHeaderParser.parseCharset(response.headers));  
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();  
            XmlPullParser xmlPullParser = factory.newPullParser();  
            xmlPullParser.setInput(new StringReader(xmlString));  
            return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));  
        } catch (UnsupportedEncodingException e) {  
            return Response.error(new ParseError(e));  
        } catch (XmlPullParserException e) {  
            return Response.error(new ParseError(e));  
        }  
    }  
  
    @Override  
    protected void deliverResponse(XmlPullParser response) {  
        mListener.onResponse(response);  
    }  
  
}
复制代码
复制代码
复制代码

这里用到了XmlPull的知识,如果不是很了解,可以去这篇文章看看:http://www.cnblogs.com/tianzhijiexian/p/4020250.html

测试代码:

复制代码
复制代码
复制代码
XMLRequest xmlRequest = new XMLRequest(  
                "http://flash.weather.com.cn/wmaps/xml/china.xml",  
                new Response.Listener<XmlPullParser>() {  
                    @Override  
                    public void onResponse(XmlPullParser response) {  
                        try {  
                            int eventType = response.getEventType();  
                            while (eventType != XmlPullParser.END_DOCUMENT) {  
                                switch (eventType) {  
                                case XmlPullParser.START_TAG:  
                                    String nodeName = response.getName();  
                                    if ("city".equals(nodeName)) {  
                                        String pName = response.getAttributeValue(0);  
                                        Log.d("TAG", "pName is " + pName);  
                                    }  
                                    break;  
                                }  
                                eventType = response.next();  
                            }  
                        } catch (XmlPullParserException e) {  
                            e.printStackTrace();  
                        } catch (IOException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                }, new Response.ErrorListener() {  
                    @Override  
                    public void onErrorResponse(VolleyError error) {  
                        Log.e("TAG", error.getMessage(), error);  
                    }  
                });  
        mQueue.add(xmlRequest);
复制代码
复制代码
复制代码

结果:

%title插图%num

 

四、自定义GsonRequest

代码来自:http://blog.csdn.net/guolin_blog/article/details/17612763

复制代码
复制代码
复制代码
public class GsonRequest<T> extends Request<T> {

    private final Listener<T> mListener;

    private Gson mGson;

    private Class<T> mClass;

    public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mGson = new Gson();
        mClass = clazz;
        mListener = listener;
    }

    public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
            ErrorListener errorListener) {
        this(Method.GET, url, clazz, listener, errorListener);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(mGson.fromJson(jsonString, mClass),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

}
复制代码
复制代码
复制代码

代码十分简单,先是将服务器响应的数据解析出来,然后通过调用Gson的fromJson方法将数据组装成对象。在deliverResponse方法中仍然是将*终的数据进行回调。

用法:

建立一个对象类,比如这里的weather、WeatherInfo类,然后初始化GsonRequest对象,*后把GsonRequest对象添加到队列中。

Weather:

 

复制代码
public class Weather {

    private WeatherInfo weatherinfo;

    public WeatherInfo getWeatherinfo() {
        return weatherinfo;
    }

    public void setWeatherinfo(WeatherInfo weatherinfo) {
        this.weatherinfo = weatherinfo;
    }

}
复制代码

 

 

 

WeatherInfo:

 

复制代码
public class WeatherInfo {

    private String city;

    private String temp;

    private String time;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getTemp() {
        return temp;
    }

    public void setTemp(String temp) {
        this.temp = temp;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

}
复制代码

 

 

 

 

Java测试代码:

复制代码
复制代码
复制代码
GsonRequest<Weather> gsonRequest = new GsonRequest<Weather>(  
        "http://www.weather.com.cn/data/sk/101010100.html", Weather.class,  
        new Response.Listener<Weather>() {  
            @Override  
            public void onResponse(Weather weather) {  
                WeatherInfo weatherInfo = weather.getWeatherinfo();  
                Log.d("TAG", "city is " + weatherInfo.getCity());  
                Log.d("TAG", "temp is " + weatherInfo.getTemp());  
                Log.d("TAG", "time is " + weatherInfo.getTime());  
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        });  
mQueue.add(gsonRequest);
复制代码
复制代码
复制代码

 

五、重写getBody()方法来添加post参数

我们可以在JsonRequest类中发现如下代码:

复制代码
复制代码
复制代码
/** 
  * Returns the raw POST or PUT body to be sent. 
  * 
  * @throws AuthFailureError in the event of auth failure 
  */  
 @Override  
 public byte[] getBody() {  
     try {  
         return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);  
     } catch (UnsupportedEncodingException uee) {  
         VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",  
                 mRequestBody, PROTOCOL_CHARSET);  
         return null;  
     }  
 }
复制代码
复制代码
复制代码

不用看代码,直接看注释,说明这里执行post请求,所以我们可以在这里设置post参数。这里

return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);

返回的就是post参数了。

如果我们想要传递POST数据,可以参考上面的代码,重写Request的getBody()方法,放入自己的参数,举例如下:

复制代码
复制代码
复制代码
   /** http请求编码方式 */  
    private static final String PROTOCOL_CHARSET = "utf-8";  
  
    private String mUserName;
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
@Override  
    public byte[] getBody() {  
        try {  
            return mUserName == null ? null : mUserName.getBytes(PROTOCOL_CHARSET);  
        } catch (UnsupportedEncodingException uee) {  
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", mUserName, PROTOCOL_CHARSET);  
            return null;  
        }  
    }
复制代码
复制代码
复制代码

完整代码(来自:http://blog.csdn.net/ttdevs/article/details/17586205):

 

复制代码
复制代码
public class CustomReqeust extends Request<String> {  
    /** http请求编码方式 */  
    private static final String PROTOCOL_CHARSET = "utf-8";  
  
    private Listener<String> mListener;  
    private String mUserName;  
  
    public CustomReqeust(String url, String userName, Listener<String> listener, ErrorListener errorListener) {  
        super(Method.POST, url, errorListener);  
        mUserName = userName;  
        mListener = listener;  
    }  
  
    @Override  
    protected Response<String> parseNetworkResponse(NetworkResponse response) {  
        String parsed;  
        try {  
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));  
        } catch (UnsupportedEncodingException e) {  
            parsed = new String(response.data);  
        }  
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));  
    }  
  
    @Override  
    protected void deliverResponse(String response) {  
        mListener.onResponse(response);  
    }  
  
    @Override  
    public byte[] getBody() {  
        try {  
            return mUserName == null ? null : mUserName.getBytes(PROTOCOL_CHARSET);  
        } catch (UnsupportedEncodingException uee) {  
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", mUserName, PROTOCOL_CHARSET);  
            return null;  
        }  
    }  
}
复制代码
复制代码

 

 

 

测试代码:

复制代码
复制代码
复制代码
private void customRequest() {  
    CustomReqeust request = new CustomReqeust(URL, "CustomVolley", new Listener<String>() {  
  
        @Override  
        public void onResponse(String arg0) {  
            Toast.makeText(getApplicationContext(), arg0, Toast.LENGTH_LONG).show();  
            Log.d("onResponse", arg0);  
        }  
    }, new ErrorListener() {  
  
        @Override  
        public void onErrorResponse(VolleyError arg0) {  
            Toast.makeText(getApplicationContext(), arg0.toString(), Toast.LENGTH_LONG).show();  
            Log.d("onErrorResponse", arg0.toString());  
        }  
    });  
    mQueue.add(request);  
}
复制代码
复制代码
复制代码

抓包结果:

%title插图%num

Volley — ImageLoader & NetworkImageView

ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的。但因为源码中没有提供磁盘缓存的设置,所以咱们还需要去源码中进行修改,让我们可以更加自如的设定是否进行磁盘缓存。

 

2. ImageLoader的用法

如 果你觉得ImageRequest已经非常好用了,那我只能说你太容易满足了 ^_^。实际上,Volley在请求网络图片方面可以做到的还远远不止这些,而ImageLoader就是一个很好的例子。ImageLoader也可以 用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效, 因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:

1. 创建一个RequestQueue对象。

2. 创建一个ImageLoader对象。

3. 获取一个ImageListener对象。

4. 调用ImageLoader的get()方法加载网络上的图片。

下面我们就来按照这个步骤,学习一下ImageLoader的用法吧。首先*步的创建RequestQueue对象我们已经写过很多遍了,相信已经不用再重复介绍了,那么就从第二步开始学习吧,新建一个ImageLoader对象,代码如下所示:

复制代码
复制代码
    ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {  
        @Override  
        public void putBitmap(String url, Bitmap bitmap) {  
        }  
      
        @Override  
        public Bitmap getBitmap(String url) {  
            return null;  
        }  
    });
复制代码
复制代码

 

 

可以看到,ImageLoader的构造函数接收两个参数,*个参数就是RequestQueue对象,第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可。

 

接下来需要获取一个ImageListener对象,代码如下所示:

    ImageListener listener = ImageLoader.getImageListener(imageView,  
            R.drawable.default_image, R.drawable.failed_image);

 

 

我 们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对 象,getImageListener()方法接收三个参数,*个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示 的图片,第三个参数指定加载图片失败的情况下显示的图片。

 

*后,调用ImageLoader的get()方法来加载图片,代码如下所示:

    imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);

 

 

get()方法接收两个参数,*个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的*大宽度和高度,如下所示:

    imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  
                    listener, 200, 200);

 

 

 

现在运行一下程序并开始加载图片,你将看到ImageView中会先显示一张默认的图片,等到网络上的图片加载完成后,ImageView则会自动显示该图,效果如下图所示。

虽然现在我们已经掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的 ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的 ImageCache,*好就要借助Android提供的LruCache功能了,如果你对LruCache还不了解,可以参考我之前的一篇文章Android高效加载大图、多图解决方案,有效避免程序OOM

这里我们新建一个BitmapCache并实现了ImageCache接口,如下所示:

复制代码
复制代码
    public class BitmapCache implements ImageCache {  
      
        private LruCache<String, Bitmap> mCache;  
      
        public BitmapCache() {  
            int maxSize = 10 * 1024 * 1024;  
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }  
            };  
        }  
      
        @Override  
        public Bitmap getBitmap(String url) {  
            return mCache.get(url);  
        }  
      
        @Override  
        public void putBitmap(String url, Bitmap bitmap) {  
            mCache.put(url, bitmap);  
        }  
      
    }
复制代码
复制代码

 

可以看到,这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入BitmapCache的实例,如下所示:

ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());

 

 

  这样我们就把ImageLoader的功能优势充分利用起来了

一、添加对磁盘缓存的控制

我们默默的打开源码,添加如下代码:

复制代码
复制代码
复制代码
    private boolean mShouldCache = true;
    /**
     * Set whether or not responses to this request should be cached(Disk Cache).
     *
     * @return This Request object to allow for chaining.
     */
    public void setShouldCache(boolean shouldCache) {
        mShouldCache = shouldCache;
    }
    
    /**
     * Returns true if responses to this request should be cached.
     */
    public final boolean shouldCache() {
        return mShouldCache;
    }
复制代码
复制代码
复制代码

定位到get方法

public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight)

找到初始化Request<Bitmap>的地方。

复制代码
复制代码
复制代码
    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

        // Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        // The bitmap did not exist in the cache, fetch it!
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // Update the caller to let them know that they should use the default bitmap.
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
复制代码
复制代码
复制代码

把红色代码中间添加:newRequest.setShouldCache(mShouldCache);*终效果如下:

复制代码
复制代码
复制代码
     // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        newRequest.setShouldCache(mShouldCache);
        mRequestQueue.add(newRequest);
复制代码
复制代码
复制代码

 

二、ImageLoader

复制代码
复制代码
复制代码
    /**
     * Constructs a new ImageLoader.
     * @param queue The RequestQueue to use for making image requests.
     * @param imageCache The cache to use as an L1 cache.
     */
    public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }
复制代码
复制代码
复制代码

初始化要传入两个参数:①RequestQueue对象;②ImageCache对象(不能传null!!!!)

RequestQueue这个我就不多说了,之前的文章已经讲解过了,下面来说说ImageCache这个对象。

 

2.1 建立ImageCache对象来实现内存缓存

ImageCache是一个图片的内存缓存对象,源码中叫做L1缓存,其实缓存分为L1、L2两种,L1就是所谓的内存缓存,将展示过的图片放入内存中进行缓存,L2就是磁盘缓存,如果这个图片下载完成,它可以被存放到磁盘中,在没有网络的时候就可以调出来使用了。

为了简单我先空实现ImageCache接口,产生一个MyImageCache对象

复制代码
复制代码
复制代码
    class MyImageCache implements ImageCache {

        @Override
        public Bitmap getBitmap(String url) {
            return null;
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
        }
    }
复制代码
复制代码
复制代码

这个接口提供的方法简单明了,今后我们可以用自己的内存缓存来完善这个类,当前getBitmap返回的是null,说明这个内存缓存没啥用处,和没缓存一样。

 

2.2 实现加载网络图片

复制代码
复制代码
复制代码
        ImageLoader imageLoader = new ImageLoader(mQueue, new MyImageCache());
        ImageListener listener = ImageLoader.getImageListener(iv, R.drawable.default_photo, R.drawable.error_photo);
        imageLoader.setShouldCache(true);
        imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener);
复制代码
复制代码
复制代码

代码的思路是产生ImageLoader后,再初始化一个监听器,监听器中传入imageview对象,还有默认的图片,出错时展示的图片,这个很好理解。*后在imageLoader的get方法中传入URL,还有监听器对象即可。

值得注意的是,get方法还有一种变体:

imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener, 0 ,0);

这里*后传入的数值是得到图片的*大宽、高,其意义和ImageRequest中的宽、高完全一致,可以参考之前的文章。其实,如果你去源码中找找的话,你会发现这两个参数*终都是传给ImageRequest的,所以在此就不做过多讲解了。

 

2.3 设置缓存

因为我们一上来就修改了源码,所以当我们在执行get()方法前可以通过setShouldCache(false)来取消磁盘缓存,如果你不进行设置的话默认是执行磁盘缓存的。那么如何配置L1缓存呢?刚刚我们的MyImageCache仅仅是一个空实现,现在就开始来完善它。

我的想法是通过LruCache进行图片缓存,分配的缓存空间是5m。如果对LruCache不是很了解,可以看看我之前的文章:详细解读LruCache类

复制代码
复制代码
复制代码
class MyImageCache implements ImageCache {

        private LruCache<String, Bitmap> mCache;  
        
        public MyImageCache() {
            int maxSize = 5 * 1024 * 1024;  
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }  
            };
        }
        
        @Override
        public Bitmap getBitmap(String url) {
            return mCache.get(url);  
            
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            mCache.put(url, bitmap); 
        }

    }
复制代码
复制代码
复制代码

每次执行get方法时,Volley会到MyImageCache中调用getBitmap(),看看有没有内存缓存,如果你返回了null,那么Volley就会从网络上下载,如果不为null,Volley会直接把取得的bitmap展示到imageview中。当图片展示到屏幕上后(无论是这个图片是从内存中读的,还是从磁盘中读的,或者是从网络上下载的),Volley都会自动调用putBitmap,把图片放入内存中缓存起来。

说明:缓存的size是:bitmap.getRowBytes() * bitmap.getHeight(),这里getRowBytes()是返回图片每行的字节数,图片的size应该乘以高度。

 

注意:imageLoader.setShouldCache(false);仅仅是设置了不实用磁盘缓存,和内存缓存没有任何关系。如果你想要不实用内存缓存,请在自定义的ImageCache中进行处理。

 

2.4 其他方法

public final boolean shouldCache()

查看是否已经做了磁盘缓存。

 

void setShouldCache(boolean shouldCache)

设置是否运行磁盘缓存,此方法需要在get方法前使用

 

public boolean isCached(String requestUrl, int maxWidth, int maxHeight)

判断对象是否已经被缓存,传入url,还有图片的*大宽高

 

public void setBatchedResponseDelay(int newBatchedResponseDelayMs)

Sets the amount of time to wait after the first response arrives before delivering all responses. Batching can be disabled entirely by passing in 0.

设置*次响应到达后到分发所有响应之前的整体时间,单位ms,如果你设置的时间是0,那么Batching将不可用。

 

三、NetworkImageView

NetworkImageView继承自ImageView,你可以认为它是一个可以实现加载网络图片的imageview,十分简单好用。这个控件在被从父控件分离的时候,会自动取消网络请求的,即完全不用我们担心相关网络请求的生命周期问题。

3.1 XML

复制代码
复制代码
复制代码
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/network_image_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center_horizontal" />
复制代码
复制代码
复制代码

 

3.2 JAVA

复制代码
复制代码
复制代码
     NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
        networkImageView.setDefaultImageResId(R.drawable.default_photo);
        networkImageView.setErrorImageResId(R.drawable.error_photo);
        networkImageView.setImageUrl("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", imageLoader);
复制代码
复制代码
复制代码

 

3.3 设置图片的宽高

NetworkImageView没有提供任何设置图片宽高的方法,这是由于它是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心。NetworkImageView*终会始终呈现给我们一张大小比控件尺寸略大的网络图片,因为它会根据控件宽高来等比缩放原始图片,这点需要注意,如果你想要了解详细原理,请看我之前的ImageRequest介绍。

如果你不想对图片进行压缩的话,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样它就会将该图片的原始大小展示出来,不会进行任何压缩。

Volley — ImageRequest & Request简介

上 篇文章我们讲 到了如何用volley进行简单的网络请求,我们可以很容易的接受到string、JsonObjec类型的返回结果,之前的例子仅仅是一次请求,这里需 要说明volley本身就是适合高并发的,所以它可以运行你用volley在短时间内进行多次请求,并且不用去手动管理线程数。仅仅是请求文字过于基础 了,本篇将讲述如何用volley从网络下载图片。

 

一、用ImageRequest来请求图片

ImageRequest是一个图片请求对象,它继承自Request<Bitmap>,所以请求得到的结果是一个bitmap。

1.1 使用步骤

ImageRequest仍旧是一个request对象,所以使用方式和StringRequest、JsonObjectRequest、JsonArrayRequest十分相似。

步骤:

  1. 建立一个RequestQueue对象
  2. 建立一个ImageRequest对象
  3. 将ImageRequest添加到RequestQueue中

*步、第三步我们在上篇文章中已经做好了,如果不清楚的话可以去上一篇文章查看。

 

1.2 分析构造函数

源码中的构造函数是这样定义的:

    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
            Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(
                new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
    }

默认的请求方式是GET,初始化方法需要传入:图片的url,一个响应结果监听器,图片的*大宽度,图片的*大高度,图片的颜色属性,出错响应的监听器。

说明:图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示*好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小

 

参数说明:decodeConfig是图片的颜色属性,下面的几个值都可以使用。

 

 

Bitmap.Config中的颜色属性(枚举类型)
ALPHA_8
ARGB_4444 由于质量低,已经被弃用,推荐用ARGB_8888
ARGB_8888 每个像素用4byte存储
RGB_565 每个像素用2byte存储,红色占5位,绿色占6位,蓝色占5位

 

    /** Socket timeout in milliseconds for image requests */
    private static final int IMAGE_TIMEOUT_MS = 1000;

    /** Default number of retries for image requests */
    private static final int IMAGE_MAX_RETRIES = 2;

    /** Default backoff multiplier for image requests */
    private static final float IMAGE_BACKOFF_MULT = 2f;
  • 设定超时时间:1000ms;
  • *大的请求次数:2次;
  • 发生冲突时的重传延迟增加数:2f(这个应该和TCP协议有关,冲突时需要退避一段时间,然后再次请求);

 

1.3 解释maxWidth,maxHeight参数

注释中详细说明了图片宽高的意义和作用,为了便于理解我再详细说一下。

    /**
     * Creates a new image request, decoding to a maximum specified width and
     * height. If both width and height are zero, the image will be decoded to
     * its natural size. If one of the two is nonzero, that dimension will be
     * clamped and the other one will be set to preserve the image's aspect
     * ratio. If both width and height are nonzero, the image will be decoded to
     * be fit in the rectangle of dimensions width x height while keeping its
     * aspect ratio.
     *
     * @param url URL of the image
     * @param listener Listener to receive the decoded bitmap
     * @param maxWidth Maximum width to decode this bitmap to, or zero for none
     * @param maxHeight Maximum height to decode this bitmap to, or zero for
     *            none
     * @param decodeConfig Format to decode the bitmap to
     * @param errorListener Error listener, or null to ignore errors
     */

先来完整解释下注释的意思:

  • 建立一个请求对象,按照*大宽高进行解码 。
  • 如果设定的宽和高都是0,那么下载到的图片将会按照实际的大小进行解码,也就是不压缩。
  • 如果宽和高中的一个或两个值不为0,那么图片的宽/高(取决于你设定了宽还是高)会压缩至设定好的值,而另一个宽/高将会按原始比例改变。
  • 如果宽和高都不是0,那么得到的图片将会“按比例”解码到你设定的宽高,也就是说*终得到的图片大小不一定是你*初设定的大小。

举个例子:

我的图片原本像素是:850×1200.

%title插图%num

当maxWidth = 0,maxHeight = 0时,*终得到的bitmap的宽高是850×1200

当maxWidth = 0,maxHeight = 600时,得到的bitmap是425×600.这就说明它会按照一个不为0的边的值,将图片进行等比缩放。

当maxWidth = 100,maxHeight = 600时,我们得到的bitmap竟然是100×141,是按照100进行等比缩小后的图片,而不是100×600.

要弄清这个问题,我们还得看源码,源码中解析响应结果的方法叫做doParse(…)

    /**
     * The real guts of parseNetworkResponse. Broken out for readability.
     */
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
       // 如果宽高都是0,那么就返回原始尺寸
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.
      // 如果我们已经重设了image的尺寸(宽高中有一个或两个不为0),那么先得到原始的大小

            decodeOptions.inJustDecodeBounds = true; // 设置先不得到bitmap,仅仅获取bitmap的参数。
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); // *次解码,主要获得的是bitmap的实际宽、高
            int actualWidth = decodeOptions.outWidth; // 得到bitmap的宽
            int actualHeight = decodeOptions.outHeight; // 得到bitmap的高

            // Then compute the dimensions we would ideally like to decode to.
            // 然后计算我们想要得到的*终尺寸
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);

            // Decode to the nearest power of two scaling factor.
            // 把图片解码到*接近2的幂次方的大小
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // If necessary, scale down to the maximal acceptable size.
            // 如果有必要的话,把得到的bitmap的*大边进行压缩来适应尺寸
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                // 通过createScaledBitmap来压缩到目标尺寸
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                        desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }
    
    /**
     * Returns the largest power-of-two divisor for use in downscaling a bitmap
     * that will not result in the scaling past the desired dimensions.
     *
     * @param actualWidth Actual width of the bitmap
     * @param actualHeight Actual height of the bitmap
     * @param desiredWidth Desired width of the bitmap
     * @param desiredHeight Desired height of the bitmap
     */
    // Visible for testing.
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
                // 计算inSampleSize的方法,详细知识自行百度吧。*终原图会被压缩为inSampleSize分之一
                // inSampleSize的值计算出来都是2的幂次方
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }

此时我们发现重要的方法是getResizedDimension,它*终确定了图片的*终尺寸。

    /**
     * Scales one side of a rectangle to fit aspect ratio.
     *
     * @param maxPrimary Maximum size of the primary dimension (i.e. width for
     *        max width), or zero to maintain aspect ratio with secondary
     *        dimension
     * @param maxSecondary Maximum size of the secondary dimension, or zero to
     *        maintain aspect ratio with primary dimension
     * @param actualPrimary Actual size of the primary dimension
     * @param actualSecondary Actual size of the secondary dimension
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
            int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

在我们目标宽、高都不为0时会调用下面的代码段:

double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }

它会计算一个ratio(比值),这就是为啥它会按比例缩小的原因。

 

1.4 初始化对象并使用

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 图片的宽度,如果是0,就不会进行压缩,否则会根据数值进行压缩
                0, // 图片的高度,如果是0,就不进行压缩,否则会压缩
                Config.ARGB_8888, // 图片的颜色属性
                new ResponseErrorListener());

监听器:

    private class ResponseListener implements Response.Listener<Bitmap> {

        @Override
        public void onResponse(Bitmap response) {
           //  Log.d("TAG", "-------------\n" + response.toString());
            iv.setImageBitmap(response);
        }
    }

    private class ResponseErrorListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error);
        }
    }

*后将其添加到请求队列即可:

 mQueue.add(imageRequest);

 

1.5   题外话

这 样我们就用volley获得了网络图片,代码也十分简单。你可能会说,有没有其 他的,更好的方式来获取图片呢?当然有的,比如volley还提供了ImageLoader、NetworkImageView这样的对象,它们可以更加 方便的获取图片。值得一提的是这两个对象的内部都是使用了ImageRequest进行操作的,也就是说imageRequest是本质,这也就是为啥我 专门写一篇来分析ImageRequest的原因。

说话要言之有理,所以贴上ImageLoader、NetworkImageView源码中部分片段来证明其内部确实是用了ImageRequest。

 

ImageLoader的源码片段:

    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // ………// The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        newRequest.setShouldCache(mShouldCache);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
    protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight, final String cacheKey) {
        return new ImageRequest(requestUrl, new Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                onGetImageSuccess(cacheKey, response);
            }
        }, maxWidth, maxHeight,
        Config.RGB_565, new ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                onGetImageError(cacheKey, error);
            }
        });
    }

在 ImageLoader重要的get()方法中,建立了一个newRequest对象,并将其放入请求队列中。这里的newRequest是通过 makeImageRequest()来产生的,而makeImageRequest()实际是返回了一个ImageRequest对象。所以用到了 ImageRequest对象。

 

NetworkImageView的源码片段:

    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

它本身就调用的是ImageLoader对象,所以自然也是用到了ImageRequest。

 

二、Request简介

2.1 前言

Request是Volley中**核心的类,之前讲到的对象都是它的子类。从字面意思看,这个对象是用来执行请求的,但通过之前的使用我们发现,它还做了很多别的事情。先贴一个Request的子类。

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 图片的宽度,如果是0,就不会进行压缩,否则会根据数值进行压缩
                0, // 图片的高度,如果是0,就不进行压缩,否则会压缩
                Config.ARGB_8888, // 图片的颜色属性
                new ResponseErrorListener());

从中我们可以发现这个ImageRequest中传入了请求的url,毕竟是request嘛,请求的url是必须的,但我们还发现这个请求对象还处理了两个监听器,这就说明它不仅仅做了请求,同时对于响应的结果也做了分发处理。

 

2.2 部分API

getCacheKey()

Returns the cache key for this request. By default, this is the URL.

返回这个请求对象中缓存对象的key,默认返回的是请求的URL

 

getBodyContentType()

Returns the content type of the POST or PUT body.

返回POST或PUT请求内容的类型,我测试的结果是:application/x-www-form-urlencoded; charset=UTF-8

从源码就能看出,默认的编码方式是UTF-8:

/**
     * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    /**
     * Returns the content type of the POST or PUT body.
     */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

 

getSequence()

Returns the sequence number of this request.

返回请求的序列数

 

getUrl()

Returns the URL of this request.

返回请求的URL

 

setShouldCache(boolean bl)

Set whether or not responses to this request should be cached.

设 置这个请求是否有缓存,这个缓存是磁盘缓存,和内存缓存没什么事情,默认是true,也就是说如果你不设置为false,这个请求就会在磁盘中进行缓存。 其实,之前讲的的StringRequest,JsonRequest,ImageRequest得到的数据都会被缓存,无论是Json数据,还是图片都 会自动的缓存起来。然而,一旦你设置setShouldCache(false),这些数据就不会被缓存了。

 

getBody()

Returns the raw POST or PUT body to be sent.

返回POST或PUT的请求体

 

deliverError()

分发错误信息,这个就是调用监听器的方法,贴源码就明白了。

    /**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

 

setRetryPolicy(RetryPolicy retryPolicy)

对一个request的重新请求策略的设置,不同的项目是否需要重新请求,重新请求几次,请求超时的时间,这些就在这设置到里面。

  /**
     * Sets the retry policy for this request.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

从上面的源码可以看出,这里需要传入一个RetryPlicy的子类,就是重新请求策略的子类,Volley会在构造Request时传一个默认的对象,叫做DefaultRetryPolicy。

    /**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

如果你对于网络请求有具体的要求,可以实现RetryPolicy接口,进行自由的配置。下面贴一下DefaultRetryPolicy源码,方便参考。

 

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

/**
 * Default retry policy for requests.
 */
public class DefaultRetryPolicy implements RetryPolicy {
    /** The current timeout in milliseconds. */
    private int mCurrentTimeoutMs;

    /** The current retry count. */
    private int mCurrentRetryCount;

    /** The maximum number of attempts. */
    private final int mMaxNumRetries;

    /** The backoff multiplier for the policy. */
    private final float mBackoffMultiplier;

    /** The default socket timeout in milliseconds */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** The default number of retries */
    public static final int DEFAULT_MAX_RETRIES = 1;

    /** The default backoff multiplier */
    public static final float DEFAULT_BACKOFF_MULT = 1f;

    /**
     * Constructs a new retry policy using the default timeouts.
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    /**
     * Constructs a new retry policy.
     * @param initialTimeoutMs The initial timeout for the policy.
     * @param maxNumRetries The maximum number of retries.
     * @param backoffMultiplier Backoff multiplier for the policy.
     */
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }

    /**
     * Returns the current timeout.
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * Returns the current retry count.
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }

    /**
     * Returns the backoff multiplier for the policy.
     */
    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    /**
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     */
    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

 

 

2.3 产生Request对象

虽然我们在代码中都会初始化一个Request对象,但是我们要在把他添加到响应队列中后才能得到它的完整体。

public <T> Request<T> add(Request<T> request) {

举例:

com.android.volley.Request<Bitmap> bitmapRequest = mQueue.add(imageRequest);

说明:如果你要设定这个request是不需要进行磁盘缓存的,那么请在把它添加到响应队列之前就进行设置,否则会得到不想要的效果。原因:源码在添加队列时会判断是否需要缓存。

    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request; // 如果不需要缓存,直接返回request对象,不会执行下面的代码
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

 

Volley — 框架简介

一、引言

虽然网上已经有很多大神、高手都写过了类似的帖子,但作为新人,必须要走模仿的道路,再考虑超越,因此学习大神的笔记,记录自己的理解,是一个菜鸟走向成功的必经之路啊。如签名所言,记录自己摸爬滚打的经历,享受不悔的青春。废话不多说,言归正传。

二、Volley是什么?

Volley是 Google 推出的 Android 异步网络请求框架和图片加载框架。

三、Volley的主要特性

(1). 提供对网络请求自动调控的管理功能RequestQueue请求队列
(2).提供对网络请求进行缓存的功能,数据可以二级缓存,图片可以三级缓存,缓存有效时间默认使用请求响应头内CacheControl设置的缓存有效时间,也就是缓存有效时间由服务端动态确定
(3). 提供强大的请求取消功能
(4). 提供简便的图片加载工具ImageLoader&NetworkImageView

(5).提供强大的自定制功能,可以自己按照自己的实际需求定制request<T>子类

四、volley的总体设计图

%title插图%num

上图源自网络。如上图所示,volley主要是通过两种Diapatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取 得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。

五、volley中的一些概念

  • Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
  • Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest都是它的子类,表示某种类型的请求。
  • RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、 NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
  • CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求 处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入 NetworkDispatcher去调度处理。
  • NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
  • ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
  • HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
  • Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
  • Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

六、volley请求处理流程图

%title插图%num

上图源自网络。

Volley基本Request对象 & RequestQueue&请求取消

Volley它非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。 所以不建议用它去进行下载文件、加载大图的操作。有人可能会问,如果我服务器中的图片都挺大的,activity中listview要加载这些图片,是不 是不能用这个框架呢?其实这个问题本身就是错误的,你想如果你服务器的图片都是大图,你要在手机上用照片墙进行展示,下载图片都会慢的要死,这个本身就是 不可行的。所以在项目初期就应该建立好服务器端的小图,照片墙用加载小图,点击后再从网络上下载大图,这才是正确思路。这时你就可以用volley加载小 图了,如果是要加载大图,可以用别的算法,强烈建议手动完成大图清除的工作,否则很可能会出现OOM。Volley本身没有做什么回收算法,还是用*基本 的GC,实际使用中可以根据需要自定义一下。

零、准备工作

Git项目,添加为lib,申请权限

git clone https://android.googlesource.com/platform/frameworks/volley
<uses-permission android:name="android.permission.INTERNET" />

 

一、初始化请求对象——RequestQueue

public class MyApplication extends Application {

    public static RequestQueue requestQueue;

    @Override
    public void onCreate() {
        super.onCreate();
        // 不必为每一次HTTP请求都创建一个RequestQueue对象,推荐在application中初始化
        requestQueue = Volley.newRequestQueue(this);
    }
}

既然是Http操作,自然有请求和响应,RequestQueue是一个请求队列 对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常合适高并发的,因此我们不必为 每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的。所以在这里我建立了一个application,然后用单例模式定义了 这个对象。当然,你可以选择在一个activity中定义一个RequestQueue对象,但这样可能会比较麻烦,而且还可能出现请求队列包含 activity强引用的问题,因此我还是推荐在application中定义。

 

二、使用StringRequest接收String类型的响应

前 面定义了请求对象,那么自然就有接收响应的对象了,这个框架中有多个响应对象,像StringRequest接受到的响应就是string类型 的;JsonRequest接收的响应就是Json类型对象。其实它们都是继承自Request<T>,然后根据不同的响应数据来进行特殊的 处理。

%title插图%num

 

2.1 初始化

**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener)
    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

这就是StringRequest的两个构造函数,不同之处是一个传入了一个method的参数,一个没有。其实看上面的源码就知道,如果你不传入method,默认会调用GET方式进行请求。当你传入了Method.POST就会用post来请求。

【参数解释】

url:请求的地址

listener:响应成功的监听器

errorListener:出错时的监听器

StringRequest getStringRequest = new StringRequest("http://www.baidu.com", new ResponseListener(), new ResponseErrorListener());
StringRequest postStringRequest = new StringRequest(Method.POST, "http://www.baidu.com", new ResponseListener(),null);

 

2.2 配置监听器

/**
     * @author:Jack Tony
     * @description  :设置响应结果监听器,因为用的是StringRequest,所以这里的结果我定义为string类型
     * @date  :2015年1月24日
     */
    private class ResponseListener implements Response.Listener<String>{

        @Override
        public void onResponse(String response) {
            // TODO 自动生成的方法存根
             Log.d("TAG", "-------------\n" + response);  
        }
    }
    
    
    /**
     * @author:Jack Tony
     * @description  :访问出错时触发的监听器
     * @date  :2015年1月28日
     */
    private class ResponseErrorListener implements Response.ErrorListener{

        @Override
        public void onErrorResponse(VolleyError error) {
            // TODO 自动生成的方法存根
             Log.e("TAG", error.getMessage(), error);  
        }
        
    }

这两个监听器没啥可说的,因为是StringRequest调用的,所以成功时触发的监听器中得到的response就是String类型。如果访问出错,那么就打印出错信息。

 

2.3 执行GET请求

现在我们有了请求对象和响应对象,外加处理响应结果的监听器,那么就执行*后一步——发送请求。发送请求很简单,将响应对象添加到请求队列即可。

      mQueue.add(getStringRequest);

完整代码:

        RequestQueue mQueue = MyApplication.requestQueue;
        StringRequest getStringRequest = new StringRequest("http://www.baidu.com", new ResponseListener(), new ResponseErrorListener());
        mQueue.add(getStringRequest);

通过简单的add()方法就直接发送了请求,如果服务器响应了请求就会触发我们的结果监听器,然后被打印出啦。现在请求的是百度,所以得到了网页的源码:

%title插图%num

 

2.4 执行POST请求

POST和GET一样,仅仅是传入的方法不同。但一般我们的post都是要带一些参数的,volley没有提供附加参数的方法,所以我们必须要在StringRequest的匿名类中重写getParams()方法:

StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
    @Override  
    protected Map<String, String> getParams() throws AuthFailureError {  
        Map<String, String> map = new HashMap<String, String>();  
        map.put("params1", "value1");  
        map.put("params2", "value2");  
        return map;  
    }  
};

这 样就传入了value1和value2两个参数了。现在可能有人会问为啥这个框架不提供这个传参的方法,还非得让我们重写。我个人觉得这个框架本身的目的 就是执行频繁的网络请求,比如下载图片,解析json数据什么的,用GET就能很好的实现了,所以就没有提供传参的POST方法。为了简单起见,我重写了 Request类中的getParams(),添加了传参的方法,以后通过setParams()就可以传参数了。

重写的代码块:

    Map<String, String> mParams = null;
    
    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return mParams;
    }

    public void setParams(Map<String, String> params){
        mParams = params;
    }

完整代码:

 

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;

import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;

import com.android.volley.VolleyLog.MarkerLog;

/**
 * Base class for all network requests.
 *
 * @param <T> The type of parsed response this request expects.
 */
public abstract class Request<T> implements Comparable<Request<T>> {

    /**
     * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    /**
     * Supported request methods.
     */
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }

    /** An event log tracing the lifetime of this request; for debugging. */
    private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;

    /**
     * Request method of this request.  Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
     * TRACE, and PATCH.
     */
    private final int mMethod;

    /** URL of this request. */
    private final String mUrl;

    /** Default tag for {@link TrafficStats}. */
    private final int mDefaultTrafficStatsTag;

    /** Listener interface for errors. */
    private final Response.ErrorListener mErrorListener;

    /** Sequence number of this request, used to enforce FIFO ordering. */
    private Integer mSequence;

    /** The request queue this request is associated with. */
    private RequestQueue mRequestQueue;

    /** Whether or not responses to this request should be cached. */
    private boolean mShouldCache = true;

    /** Whether or not this request has been canceled. */
    private boolean mCanceled = false;

    /** Whether or not a response has been delivered for this request yet. */
    private boolean mResponseDelivered = false;

    // A cheap variant of request tracing used to dump slow requests.
    private long mRequestBirthTime = 0;

    /** Threshold at which we should log the request (even when debug logging is not enabled). */
    private static final long SLOW_REQUEST_THRESHOLD_MS = 3000;

    /** The retry policy for this request. */
    private RetryPolicy mRetryPolicy;

    /**
     * When a request can be retrieved from cache but must be refreshed from
     * the network, the cache entry will be stored here so that in the event of
     * a "Not Modified" response, we can be sure it hasn't been evicted from cache.
     */
    private Cache.Entry mCacheEntry = null;

    /** An opaque token tagging this request; used for bulk cancellation. */
    private Object mTag;

    /**
     * Creates a new request with the given URL and error listener.  Note that
     * the normal response listener is not provided here as delivery of responses
     * is provided by subclasses, who have a better idea of how to deliver an
     * already-parsed response.
     *
     * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
     */
    @Deprecated
    public Request(String url, Response.ErrorListener listener) {
        this(Method.DEPRECATED_GET_OR_POST, url, listener);
    }

    /**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

    /**
     * Return the method for this request.  Can be one of the values in {@link Method}.
     */
    public int getMethod() {
        return mMethod;
    }

    /**
     * Set a tag on this request. Can be used to cancel all requests with this
     * tag by {@link RequestQueue#cancelAll(Object)}.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setTag(Object tag) {
        mTag = tag;
        return this;
    }

    /**
     * Returns this request's tag.
     * @see Request#setTag(Object)
     */
    public Object getTag() {
        return mTag;
    }

    /**
     * @return this request's {@link com.android.volley.Response.ErrorListener}.
     */
    public Response.ErrorListener getErrorListener() {
        return mErrorListener;
    }

    /**
     * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)}
     */
    public int getTrafficStatsTag() {
        return mDefaultTrafficStatsTag;
    }

    /**
     * @return The hashcode of the URL's host component, or 0 if there is none.
     */
    private static int findDefaultTrafficStatsTag(String url) {
        if (!TextUtils.isEmpty(url)) {
            Uri uri = Uri.parse(url);
            if (uri != null) {
                String host = uri.getHost();
                if (host != null) {
                    return host.hashCode();
                }
            }
        }
        return 0;
    }

    /**
     * Sets the retry policy for this request.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

    /**
     * Adds an event to this request's event log; for debugging.
     */
    public void addMarker(String tag) {
        if (MarkerLog.ENABLED) {
            mEventLog.add(tag, Thread.currentThread().getId());
        } else if (mRequestBirthTime == 0) {
            mRequestBirthTime = SystemClock.elapsedRealtime();
        }
    }

    /**
     * Notifies the request queue that this request has finished (successfully or with error).
     *
     * <p>Also dumps all events from this request's event log; for debugging.</p>
     */
    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }

            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        } else {
            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
                VolleyLog.d("%d ms: %s", requestTime, this.toString());
            }
        }
    }

    /**
     * Associates this request with the given queue. The request queue will be notified when this
     * request has finished.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRequestQueue(RequestQueue requestQueue) {
        mRequestQueue = requestQueue;
        return this;
    }

    /**
     * Sets the sequence number of this request.  Used by {@link RequestQueue}.
     *
     * @return This Request object to allow for chaining.
     */
    public final Request<?> setSequence(int sequence) {
        mSequence = sequence;
        return this;
    }

    /**
     * Returns the sequence number of this request.
     */
    public final int getSequence() {
        if (mSequence == null) {
            throw new IllegalStateException("getSequence called before setSequence");
        }
        return mSequence;
    }

    /**
     * Returns the URL of this request.
     */
    public String getUrl() {
        return mUrl;
    }

    /**
     * Returns the cache key for this request.  By default, this is the URL.
     */
    public String getCacheKey() {
        return getUrl();
    }

    /**
     * Annotates this request with an entry retrieved for it from cache.
     * Used for cache coherency support.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setCacheEntry(Cache.Entry entry) {
        mCacheEntry = entry;
        return this;
    }

    /**
     * Returns the annotated cache entry, or null if there isn't one.
     */
    public Cache.Entry getCacheEntry() {
        return mCacheEntry;
    }

    /**
     * Mark this request as canceled.  No callback will be delivered.
     */
    public void cancel() {
        mCanceled = true;
    }

    /**
     * Returns true if this request has been canceled.
     */
    public boolean isCanceled() {
        return mCanceled;
    }

    /**
     * Returns a list of extra HTTP headers to go along with this request. Can
     * throw {@link AuthFailureError} as authentication may be required to
     * provide these values.
     * @throws AuthFailureError In the event of auth failure
     */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    /**
     * Returns a Map of POST parameters to be used for this request, or null if
     * a simple GET should be used.  Can throw {@link AuthFailureError} as
     * authentication may be required to provide these values.
     *
     * <p>Note that only one of getPostParams() and getPostBody() can return a non-null
     * value.</p>
     * @throws AuthFailureError In the event of auth failure
     *
     * @deprecated Use {@link #getParams()} instead.
     */
    @Deprecated
    protected Map<String, String> getPostParams() throws AuthFailureError {
        return getParams();
    }

    /**
     * Returns which encoding should be used when converting POST parameters returned by
     * {@link #getPostParams()} into a raw POST body.
     *
     * <p>This controls both encodings:
     * <ol>
     *     <li>The string encoding used when converting parameter names and values into bytes prior
     *         to URL encoding them.</li>
     *     <li>The string encoding used when converting the URL encoded parameters into a raw
     *         byte array.</li>
     * </ol>
     *
     * @deprecated Use {@link #getParamsEncoding()} instead.
     */
    @Deprecated
    protected String getPostParamsEncoding() {
        return getParamsEncoding();
    }

    /**
     * @deprecated Use {@link #getBodyContentType()} instead.
     */
    @Deprecated
    public String getPostBodyContentType() {
        return getBodyContentType();
    }

    /**
     * Returns the raw POST body to be sent.
     *
     * @throws AuthFailureError In the event of auth failure
     *
     * @deprecated Use {@link #getBody()} instead.
     */
    @Deprecated
    public byte[] getPostBody() throws AuthFailureError {
        // Note: For compatibility with legacy clients of volley, this implementation must remain
        // here instead of simply calling the getBody() function because this function must
        // call getPostParams() and getPostParamsEncoding() since legacy clients would have
        // overridden these two member functions for POST requests.
        Map<String, String> postParams = getPostParams();
        if (postParams != null && postParams.size() > 0) {
            return encodeParameters(postParams, getPostParamsEncoding());
        }
        return null;
    }
 
    Map<String, String> mParams = null;
    
    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return mParams;
    }

    public void setParams(Map<String, String> params){
        mParams = params;
    }
    
    /**
     * Returns which encoding should be used when converting POST or PUT parameters returned by
     * {@link #getParams()} into a raw POST or PUT body.
     *
     * <p>This controls both encodings:
     * <ol>
     *     <li>The string encoding used when converting parameter names and values into bytes prior
     *         to URL encoding them.</li>
     *     <li>The string encoding used when converting the URL encoded parameters into a raw
     *         byte array.</li>
     * </ol>
     */
    protected String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    /**
     * Returns the content type of the POST or PUT body.
     */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

    /**
     * Returns the raw POST or PUT body to be sent.
     *
     * <p>By default, the body consists of the request parameters in
     * application/x-www-form-urlencoded format. When overriding this method, consider overriding
     * {@link #getBodyContentType()} as well to match the new body format.
     *
     * @throws AuthFailureError in the event of auth failure
     */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /**
     * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
     */
    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
        StringBuilder encodedParams = new StringBuilder();
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                encodedParams.append('=');
                encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
                encodedParams.append('&');
            }
            return encodedParams.toString().getBytes(paramsEncoding);
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
        }
    }

    /**
     * Set whether or not responses to this request should be cached.
     *
     * @return This Request object to allow for chaining.
     */
    public final Request<?> setShouldCache(boolean shouldCache) {
        mShouldCache = shouldCache;
        return this;
    }

    /**
     * Returns true if responses to this request should be cached.
     */
    public final boolean shouldCache() {
        return mShouldCache;
    }

    /**
     * Priority values.  Requests will be processed from higher priorities to
     * lower priorities, in FIFO order.
     */
    public enum Priority {
        LOW,
        NORMAL,
        HIGH,
        IMMEDIATE
    }

    /**
     * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
     */
    public Priority getPriority() {
        return Priority.NORMAL;
    }

    /**
     * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
     * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
     * attempts remaining, this will cause delivery of a {@link TimeoutError} error.
     */
    public final int getTimeoutMs() {
        return mRetryPolicy.getCurrentTimeout();
    }

    /**
     * Returns the retry policy that should be used  for this request.
     */
    public RetryPolicy getRetryPolicy() {
        return mRetryPolicy;
    }

    /**
     * Mark this request as having a response delivered on it.  This can be used
     * later in the request's lifetime for suppressing identical responses.
     */
    public void markDelivered() {
        mResponseDelivered = true;
    }

    /**
     * Returns true if this request has had a response delivered for it.
     */
    public boolean hasHadResponseDelivered() {
        return mResponseDelivered;
    }

    /**
     * Subclasses must implement this to parse the raw network response
     * and return an appropriate response type. This method will be
     * called from a worker thread.  The response will not be delivered
     * if you return null.
     * @param response Response from the network
     * @return The parsed response, or null in the case of an error
     */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    /**
     * Subclasses can override this method to parse 'networkError' and return a more specific error.
     *
     * <p>The default implementation just returns the passed 'networkError'.</p>
     *
     * @param volleyError the error retrieved from the network
     * @return an NetworkError augmented with additional information
     */
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        return volleyError;
    }

    /**
     * Subclasses must implement this to perform delivery of the parsed
     * response to their listeners.  The given response is guaranteed to
     * be non-null; responses that fail to parse are not delivered.
     * @param response The parsed response returned by
     * {@link #parseNetworkResponse(NetworkResponse)}
     */
    abstract protected void deliverResponse(T response);

    /**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

    /**
     * Our comparator sorts from high to low priority, and secondarily by
     * sequence number to provide FIFO ordering.
     */
    @Override
    public int compareTo(Request<T> other) {
        Priority left = this.getPriority();
        Priority right = other.getPriority();

        // High-priority requests are "lesser" so they are sorted to the front.
        // Equal priorities are sorted by sequence number to provide FIFO ordering.
        return left == right ?
                this.mSequence - other.mSequence :
                right.ordinal() - left.ordinal();
    }

    @Override
    public String toString() {
        String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
        return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " "
                + getPriority() + " " + mSequence;
    }
}

 

 

 

使用示例:

        StringRequest postStringRequest = new StringRequest(Method.POST, "http://m.weather.com.cn/data/101010100.html",
                new ResponseListener(), null);
        Map<String, String> map = new HashMap<String, String>();
        map.put("params1", "value1");
        map.put("params2", "value2");
        postStringRequest.setParams(map);
        mQueue.add(postStringRequest);

结果:

%title插图%num

 

三、使用JsonObjectRequest接收Json类型的响应

类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest,从名字上你应该能就看出它们的区别了吧?一个是用于请求一段JSON数据的,一个是用于请求一段JSON数组的。

 

3.1 构造函数

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
     Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
                    errorListener);
    }

    /**
     * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
     * <code>null</code>, <code>POST</code> otherwise.
     *
     * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
     */
    public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
            ErrorListener errorListener) {
        this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                listener, errorListener);
    }

 

3.2 发送请求

和之前讲过的StringRequest一样,可以传入请求的类型,如果没传就默认是GET请求。参数也是如出一辙,就是泛型变了下。定义和使用的方式也完全一致,初始化对象后,添加到请求队列即可。

        JsonObjectRequest request = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html",
                null, new ResponseListener(), new ResponseErrorListener());
        mQueue.add(request);
    /**
     * @author:Jack Tony
     * @description :设置响应结果监听器,这里用的是JsonObjectRequest,所以返回的结果是JSONObject
     * @date :2015年1月24日
     */
    private class ResponseListener implements Response.Listener<JSONObject> {

        @Override
        public void onResponse(JSONObject response) {
            // TODO 自动生成的方法存根
            Log.d("TAG", "-------------\n" + response.toString());
        }

    }

    /**
     * @author:Jack Tony
     * @description :访问出错时触发的监听器
     * @date :2015年1月28日
     */
    private class ResponseErrorListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error);
        }
    }

 

结果:

你怎么查看解析是否成功了呢?服务器端的数据:

{"weatherinfo":{"city":"北京","city_en":"beijing","date_y":"2014年3月4日","date":"","week":"星期二","fchh":"11","cityid":"101010100","temp1":"8℃~-3℃","temp2":"8℃~-3℃","temp3":"7℃~-3℃","temp4":"8℃~-1℃","temp5":"10℃~1℃","temp6":"10℃~2℃","tempF1":"46.4℉~26.6℉","tempF2":"46.4℉~26.6℉","tempF3":"44.6℉~26.6℉","tempF4":"46.4℉~30.2℉","tempF5":"50℉~33.8℉","tempF6":"50℉~35.6℉","weather1":"晴","weather2":"晴","weather3":"晴","weather4":"晴转多云","weather5":"多云","weather6":"多云","img1":"0","img2":"99","img3":"0","img4":"99","img5":"0","img6":"99","img7":"0","img8":"1","img9":"1","img10":"99","img11":"1","img12":"99","img_single":"0","img_title1":"晴","img_title2":"晴","img_title3":"晴","img_title4":"晴","img_title5":"晴","img_title6":"晴","img_title7":"晴","img_title8":"多云","img_title9":"多云","img_title10":"多云","img_title11":"多云","img_title12":"多云","img_title_single":"晴","wind1":"北风4-5级转微风","wind2":"微风","wind3":"微风","wind4":"微风","wind5":"微风","wind6":"微风","fx1":"北风","fx2":"微风","fl1":"4-5级转小于3级","fl2":"小于3级","fl3":"小于3级","fl4":"小于3级","fl5":"小于3级","fl6":"小于3级","index":"寒冷","index_d":"天气寒冷,建议着厚羽绒服、毛皮大衣加厚毛衣等隆冬服装。年老体弱者尤其要注意保暖防冻。","index48":"冷","index48_d":"天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。","index_uv":"中等","index48_uv":"中等","index_xc":"较适宜","index_tr":"一般","index_co":"较舒适","st1":"7","st2":"-3","st3":"8","st4":"0","st5":"7","st6":"-1","index_cl":"较不宜","index_ls":"基本适宜","index_ag":"易发"}}

如果解析错误,就会出现警告,这时错误监听器就会被触发:

%title插图%num

如果解析成功,就不会出现错误,这就是泛型的好处,保证了程序的正确性。

%title插图%num

*终我们就可以在Response.Listener<JSONObject>中得到JSONObject对象,通过这个对象就能进行下一步的处理了。

 

3.3 解析Json

比如要解析出上面Json数据中city的字段,就可以按照如下方式编码:

     try {
                response = response.getJSONObject("weatherinfo");
                Log.i(TAG, "City = " + response.getString("city"));
            } catch (JSONException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }

完整监听器代码:

    private class ResponseListener implements Response.Listener<JSONObject> {

        @Override
        public void onResponse(JSONObject response) {
            // TODO 自动生成的方法存根
            Log.d("TAG", "-------------\n" + response.toString());
            
            try {
                response = response.getJSONObject("weatherinfo");
                Log.i(TAG, "City = " + response.getString("city"));
            } catch (JSONException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
        }

    }

结果:

%title插图%num

 

四、JsonArrayRequest简介

除此之外,还有一个相关的响应对象叫做JsonArrayRequest,这个获得的就是一个Json序列,使用方式没有任何改变,这里就不做过多介绍了,因为剩下的就是Json的知识了,和Volley没有任何关系。

源码:

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley.toolbox;

import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;

import org.json.JSONArray;
import org.json.JSONException;

import java.io.UnsupportedEncodingException;

/**
 * A request for retrieving a {@link JSONArray} response body at a given URL.
 */
public class JsonArrayRequest extends JsonRequest<JSONArray> {

    /**
     * Creates a new request.
     * @param url URL to fetch the JSON from
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) {
        super(Method.GET, url, null, listener, errorListener);
    }

    @Override
    protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString =
                new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(new JSONArray(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}

通过源码我们知道,这个响应对象发送的请求是Get,而且它是继承自JsonRequest,如果你想用POST来做,自行添加新的构造函数即可。

 

四、请求取消

      Activity里面启动了网络请求,而在这个网络请求还没返回结果的时候,Activity被结束了,此时如果继续使用其中的Context等,除了无辜的浪费CPU,电池,网络等资源,有可能还会导致程序crash,所以,我们需要处理这种一场情况。使用Volley的话,我们可以在Activity停止的时候,同时取消所有或部分未完成的网络请求。Volley里所有的请求结果会返回给主进程,如果在主进程里取消了某些请求,则这些请求将不会被返回给主线程。Volley支持多种request取消方式。
1)可以针对某些个request做取消操作:

@Override  
   public void onStop() {  
       for (Request <?> req : mRequestQueue) {  
           req.cancel();  
       }  
   }

 

 

2)取消这个队列里的所有请求:

    @Override  
    protected void onStop() {  
        // TODO Auto-generated method stub  
        super.onStop();  
        mRequestQueue.cancelAll(this);  
    }

 

3)可以根据RequestFilter或者Tag来终止某些请求

给请求设置标签:

 

如果你想取消所有的请求,在onStop方法中添加如下代码:

@Override
protected void onStop() {
    super.onStop();
    mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
        @Override
        public boolean apply(Request<?> request) {
            // do I have to cancel this?
            return true; // -> always yes
        }
    });
}

 

这样你就不必担心在onResponse被调用的时候用户已经销毁Activity。这种情况下会抛出NullPointerException 异。但是post请求则需要继续,即使用户已经改变了Activity。我们可以通过使用tag来做到,在构造GET请求的时候,添加一个tag给它。

// after declaring your request
request.setTag("My Tag");
mRequestQueue.add(request);

 

如果要取消所有指定标记My Tag的请求,只需简单的添加下面的一行代码:

mRequestQueue.cancelAll("My Tag");

这样你就只会取消My Tag请求,让其它请求不受影响。注意你必须手动在销毁的Activity中处理这种情况。

@Override  
rotected void onStop() {  
// TODO Auto-generated method stub  
super.onStop();  
  
mRequestQueue.cancelAll( new RequestFilter() {});  
or  
mRequestQueue.cancelAll(new Object());

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速