volley-core

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);

第一个方法的实现调用了第二个方法,传的HeepStack的参数为null。
第二个方法中,如果HttpStack的参数为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 DiskBasedCahce(cacheDir),network);
queue.start();
return queue;

大多采用volley.newRequestQueue(context)的默认实现,构建RequestQueue.
通过上面可以看到,可以抛开Volley工具类构造自定义的RequestQueue,采用自定义HttpStack,
采用自定义的Network实现,采用自定义的Cache实现来构建RequestQueue.

2. HttpURLConnection和AndroidHttpClient 如何选择及原因:

在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。

3. 关于User Agent

通过代码,发现如果使用AndroidHttpClient,Volley还会将请求中的UserAgent字段设置为App的
${packageName}/${versionCode},如果异常则使用 “volley/0”,不过这个获取UserAgent的操作
应该放到if else 内部更合适,而对于HttpURLConnection却没有任何操作,为什么?如果使用Fiddler
或Charles对数据抓包,会发现,HttpURLConnection默认是对UserAgent,类似:

Dalvik/1.6.0 (Linux; U; Android 4.1.1; Google Nexus 4 - 4.1.1 - API 16 - 768x1280_1              Build/JRO03S)

经常用 WebView 的同学会也许会发现似曾相识,是,WebView 默认的 User-Agent 也是这个。
实际在请求发出之前,会检测 User-Agent 是否为空,如果不为空,则加上系统默认UserAgent.
在Android2.1之后,可以通过:

String userAgent = System.getProperty("http.agent");

得到系统默认的User-Agent,Volley如果希望自定义User-Agent,可在自定义Request中重写getHeaders()函数

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
   Map<String, String> headerMap = new HashMao<String, String>();
   headerMap.put("User-Agent", "android-open-project-analysis/1.0");
   return headerMap;
}
# 4.2.2 Request.java

代表一个网络请求的抽象类,通过构建一个Reqest类的非抽象子类(StringRequest,JsonRequest,
ImageRequest或自定义)对象,并将其加入到RequestQueue中来完成一次网络请求操作,
Volley支持8种Http请求方式,GET,POST,PUT,DELETE,HEAD,OPTIONS,TRACE,PATCH
Request类中包含了请求url,请求方式,请求Header,请求Body,请求的优先级等信息。

因为是抽象类,子类必须重写的两个方法。

abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

子类重写此方法,将网络返回的原生字节内容转换为合适的类型,此方法会在工作线程中被调用。

abstract protected void deliverResponse(T response);

子类重写此方法,将解析成合适类型的内容传递给它们的监听回调。

一下两个方法也经常被重写

public byte[] getBody()

重写此方法,可以构建用于POST,PUT,PATCH, 请求方式的body内容。

projected Map<String, String> getParams()

在上面getBody()函数没有被重写情况下,此方法的返回值会被key、value分别编码后拼装起来
转换为字节码作为Body的内容。

RequestQueue.java

volley 框架的核心类,将请求request加入到一个运行的RequestQueue中,来完成请求操作。

  1. 主要成员变量
    RequestQueue 中维持了两个基于优先级的Request队列,缓存请求队列和网络请求队列。
    在缓存请求队列中的Request,将通过缓存获取数据;放在网络请求队列的Request,将通过网络获取数据

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

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

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

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

private final Map<String,Queue<Request<?>>> mWaitingRequests = new HashMap<String,
Queue<Request<?>>>();
  1. 启动队列

创建出一个RequestQueue以后,调用start方法,启动队列。

/**
 * Starts the dispathers in this queue.
 */
public void start() {
   stop();
   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,
默认线程数为4,即n默认为4,存在优化的余地,比如可以根据CPU核数以及网络类型计算更合适的并发数
缓存调度线程不断的从缓存请求队列中取出request去处理,网络调度线程不断的从网络请求
队列中取出Request去处理

  1. 加入请求

    public Request add(Request request);

流程图如下:

  1. 请求完成

    void finish(Request<?> request)

Request 请求结束

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

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

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

4.2.4 CacheDispatcher.java

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

  1. 成员变量
    BlockingQueueRequest<?>> mCacheQueue 缓存请求队列
    BlockingQueue> mNetworkQueue 网络请求队列
    Cache mCache 缓存类, 代表一个可以获取请求结果,存储请求结果的缓存
    ResponseDelivery mDelivery 请求结果传递类。
  2. 处理流程图
NetworkDispatcher.java

一个线程,用来调度处理走网络的请求。启动后会不断的从网络请求队列中取得请求处理,
队列为空则等待,请求处理结束后则将结果传递给ResponseDelivery去执行后续结果,
并判断结果是否需要缓存。

1.成员变量

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

2.处理流程图

4.2.6 Cache.java

缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。

1.主要方法

public Entry get(String key); 通过key获取请求的缓存实体
public void put(String key, Entry entry); 存入一个请求的缓存实体  
public void remove(String key, Entry entry); 移除指定的缓存实体 
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)
// 检查是否能再分配 neededSpace 字节的空间,如果不能则删除缓存中部分数据
private void pruneIfNeeded(int neededSpace)
//清空缓存
public synchronized void clear() 
//删除缓存中的某个元素
public synchronized void remove(String key) 

2.CacheHeadler类

CacheHeadler是缓存文件摘要信息,存贮在缓存文件的头部,与上面的Cache.Entry相似

见core2.md