Volley学习笔记

Volley简述

Volley是Google推出的用于Android下发起Http请求的网络库。它适合于频繁的,数据量较小的通信,而在数据量大的场景下表现不佳,原因在于,Volley对于所有的Response的解析都是在内存当中做的。官方推荐在对于大量数据通信的场景下选择其他的库,例如DownloadManager.

Volley基本原理

Android+Volley+主要对象关系图.png

看图说话:
①: Volley的主要使用方法就是创建Request实例,并且将其加入到RequestQueue当中。在RequestQueue中维护了3个队列和一个Map,图中只标识最重要的两个队列NetWorkQueue和CacheQueue。如果Request不允许使用缓存,则会被直接加入到NetWorkQueue,否则被加入CacheQueue。
②:CacheDispatcher运行在单独的线程当中,不停的尝试获取CacheQueue中的Request对象。
③:CacheDispatcher一旦获取Request对象成功,就派发到Cache对象尝试获取缓存。如果找到,则直接返回Response,否则CacheDispatcher还是会将此Request对象放置于NetWorkQueue。
④: RequestQueue默认开启4个NetWorkDispatcher线程,同样不停的尝试获取NetWorkQueue当中的Request对象。
⑤: 其中某个NetWorkDispatcher获取Request对象成功,将其派发给HttpStack对象处理。HttpStack对象是真正发送接收Http报文的对象,用户可以实现它的接口,自定义发送与接收的过程。Volley默认使用HttpClient或者HttpURLConnection(根据不同SDK版本)。
⑥: HttpStack处理发送与接收。即传入Request对象,返回Response对象。

最后,无论是NetWorkDispatcher还是CacheDispatcher最后都回调Request的deliverResponse()。这也是官方文档所说的,为什么自定义的Request必须实现此方法的原因。需要注意的是,Request中必须实现的两个方法是在子线程中执行的。

Volley目录结构

volley
    │  AuthFailureError.java
    │  TimeoutError.java
    │  VolleyError.java
    │  ServerError.java
    │  ClientError.java
    │  ParseError.java
    │  NetworkError.java
    │  NoConnectionError.java
------------------------------------------------------------------------------------------
    │  Cache.java //Cache接口
    │  CacheDispatcher.java     
    │  DefaultRetryPolicy.java //重试策略,当请求发送遭遇异常,回调Request中定义的重试方法
    │  ExecutorDelivery.java //ResponseDelivery实现类
    │  Network.java
    │  NetworkDispatcher.java
------------------------------------------------------------------------------------------
    │  NetworkResponse.java //解析HTTP报文的Response数据结构
    │  Request.java//定义了Resquest数据结构
    │  RequestQueue.java//定义了RequestQueue数据结构
    │  Response.java //在本地表示的Response,由NetworkResponse解析产生,包括数据和缓存信息
------------------------------------------------------------------------------------------
    │  ResponseDelivery.java //接口
    │  RetryPolicy.java  //接口
    │  VolleyLog.java
    └─toolbox
            AndroidAuthenticator.java
            Authenticator.java
------------------------------------------------------------------------------------------
            BasicNetwork.java // Network实现类
            RequestFuture.java
------------------------------------------------------------------------------------------
            DiskBasedCache.java //Cache实现类
            NoCache.java//Cache实现类,即不使用缓存
------------------------------------------------------------------------------------------
            HttpStack.java//接口
            HttpClientStack.java //HttpStack实现类,使用HttpClient
            HurlStack.java//HttpStack实现类,使用HttpURLConnection
------------------------------------------------------------------------------------------
            ClearCacheRequest.java//Request的一种,用于清除所有缓存数据
            StringRequest.java//Request的一种,用于请求String数据
            JsonRequest.java //Request的一种,用于请求JSON数据
            JsonArrayRequest.java
            JsonObjectRequest.java
------------------------------------------------------------------------------------------
            ImageRequest.java//Request的一种,用于加载图片
            ImageLoader.java
            NetworkImageView.java
------------------------------------------------------------------------------------------
            HttpHeaderParser.java//工具类,可用于解析报文头部缓存信息
            Volley.java//工具类,方便创建RequestQueue
            PoolingByteArrayOutputStream.java
            ByteArrayPool.java//工具类,Byte数组缓存池

细节剖析 -> 缓存存取的具体实现

一. 缓存的组织形式:
DiskBasedCache是将缓存按一定格式写入文件,所以在创建DiskBasedCache需要传入指定文件路径。按照代码逻辑,每一个Cache项都单独存放在一个文件当中。初始化DiskBasedCache时,读取每一个文件中的缓存头部内容,然后保存在内存Map中。

//摘取代码关键部分,与实际代码不同
@Override
public synchronized void initialize() {
   File[] files = mRootDirectory.listFiles();
   for (File file : files) {
      fis = new BufferedInputStream(new FileInputStream(file));
      CacheHeader entry = CacheHeader.readHeader(fis);
      entry.size = file.length();
      mEntries.put(key, entry);
    }
}  

二. 获取缓存
CacheDispatcher获得Request对象,就尽力获取缓存内容。

Cache.Entry entry = mCache.get(request.getCacheKey());

DiskBasedCache中,首先从内存中查找缓存头部是否存在,如果存在,再去文件中读取缓存的具体内容。

@Override
public synchronized Entry get(String key) {
    CacheHeader entry = mEntries.get(key);
    // if the entry does not exist, return.
    if (entry == null) {
        return null;
    }
    try {
        cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
        CacheHeader.readHeader(cis); // eat header
        byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
        return entry.toCacheEntry(data);
    }
}

三. Cache Prune
Volley会记录缓存使用空间大小,当缓存空间不足时,触发Cache Prune。遍历Map,按照插入顺序进行删除,直到空间充足。

Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, CacheHeader> entry = iterator.next();
    CacheHeader e = entry.getValue();
    boolean deleted = getFileForKey(e.key).delete();
    if (deleted) {
        mTotalSize -= e.size;
    } else {
       VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
               e.key, getFilenameForKey(e.key));
    }
    iterator.remove();
    prunedFiles++;

    if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
        break;
    }
}

四. 缓存过期
判断缓存过期的两条规则:

  1. 当缓存信息的ttl小于当前系统时间,则认为过期。
  2. 缓存信息的soft_ttl小于当前系统时间,则认为过期

第二条规则有助于认为更改过期信息,强制系统进行缓存更新。
当Volley未找到缓存项或者缓存过期,则将Request加入NetworkQueue。但是很多网站采用ETag标签标识实体有无改变,在这种情况下,Volley仍然会发送网络请求,并根据返回304进行处理。下文将分析这种情况。

细节剖析 -> NetworkDispatcher或者CacheDispatcher完成后如何切换线程

ResponseDelivery持有MainThread的Handler引用,当一切都处理完毕,ResponseDelivery将Runnable对象发送给主线程的Handler。而此Runnable对象的任务就是回调Request的监听器。这里有两个非常重要的方法会被回调,一个是Request子类必须实现的deliverResponse,另外一个是可选的实现RequestFinishedListener接口的onRequestFinished,此方法的调用时机就是当Request被处理完毕。正常情况下,deliverResponse的调用时机在onRequestFinished之前。另外,当Request被中止时,onRequestFinished也会被触发。

细节剖析-> Volley处理缓存相关网络请求

如上节所述,当采用ETag标签时,即便Cache没有过期,Volley还是会发送网络请求的。
NetworkDispacher从队列中取得Request,然后交给Network发送。Network首先对Request添加Cache头,If-None-Match: Etag,并发送。NetworkDispatcher对接收到的回复报文进行处理,刷新缓存。
对于每一次的请求,Request都会回调deliverResponse,但是对于304的回复报文,什么内容都没有,所以,对于304回复正常报文,Volley会从cache中构造Response。

总结

Volley代码非常简洁易懂,模块之间划分清晰,适合学习~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容