从http开始说Volley缓存

Volley缓存

1.从何说起?

Volley是干嘛用的呢? 大家都知道是请求网络用的。那我们常用网络请求是使用HTTP协议的。那就从这个HTTP的请求和响应说起了。
我们每天使用的浏览器访问网页,都是走的HTTP协议。
打开一个网页一般都需要两个过程。我们在输入一个网址,然后按回车,或者点一个链接。这些都是我们操作的一次请求(request)。
这个请求会发送到你所输入网址或链接所映射的服务器。这个服务器收到请求之后,一般都会做出响应(response)。
这个服务器响应回来的内容会被浏览器获取到了,然后浏览器解析下响应内容,就给我们展现出来了。。
走个例子:
http://192.168.0.66/rdx/



说说HTTP请求和响应的吧。。。请求和响应都是报文的形式。报文也是网络传输的单位。这个涉及到网络方面的知识了。。

HTTP请求报文由3部分组成(   请求行+请求头+请求体   ):  

下面是一个实际的请求报文:  
①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。不过,当前的大多数浏览器只支持GET和POST。
咱们的Volley差不多都覆盖了。。

②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL
③是协议名称及版本号。
④是HTTP的报文头(请求头),报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
⑤是报文体(请求体),它将一个页面表单中的组件值通过param1=value1&param2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。

对照上面的请求报文,我们把它进一步分解,你可以看到一幅更详细的结构图:   




HTTP的响应报文也由三部分组成(   响应行+响应头+响应体   ):   
   

以下是一个实际的HTTP响应报文:   

     

①报文协议及版本;   
②状态码及状态描述;   
③响应报文头,也是由多个属性组成;   
④响应报文体,即我们真正要的“干货”。   

响应状态码      

和请求报文相比,响应报文多了一个“响应状态码”,它以“清晰明确”的语言告诉客户端本次请求的处理结果。   

HTTP的响应状态码由5段组成:   

  • 1xx 消息,一般是告诉客户端,请求已经收到了,正在处理,别急...
  • 2xx 处理成功,一般表示:请求收悉、我明白你要的、请求已受理、已经处理完成等信息.
  • 3xx 重定向到其它地方。它让客户端再发起一个请求以完成整个处理。
  • 4xx 处理发生错误,责任在客户端,如客户端的请求一个不存在的资源,客户端未被授权,禁止访问等。
  • 5xx 处理发生错误,责任在服务端,如服务端抛出异常,路由出错,HTTP版本不支持等。


以下是几个常见的状态码:   

200 OK      

你最希望看到的,即处理成功!   

303 See Other      

我把你redirect到其它的页面,目标的URL通过响应报文头的Location告诉你。   
引用
悟空:师傅给个桃吧,走了一天了    
唐僧:我哪有桃啊!去王母娘娘那找吧 


304 Not Modified      

告诉客户端,你请求的这个资源至你上次取得后,并没有更改,你直接用你本地的缓存吧,我很忙哦,你能不能少来烦我啊!   

404 Not Found      

你最不希望看到的,即找不到页面。如你在google上找到一个页面,点击这个链接返回404,表示这个页面已经被网站删除了,google那边的记录只是美好的回忆。   

500 Internal Server Error      

看到这个错误,你就应该查查服务端的日志了,肯定抛出了一堆异常,别睡了,起来改BUG去吧!   


其它的状态码参见:   http://en.wikipedia.org/wiki/List_of_HTTP_status_codes    

常见的HTTP响应报文头属性      

Cache-control
响应输出到客户端后,服务端通过该报文头属告诉客户端如何控制响应内容的缓存。   

下面,的设置让客户端对响应内容缓存3600秒,也即在3600秒内,如果客户再次访问该资源,直接从客户端的缓存中返回内容给客户,不要再从服务端获取
(当然,这个功能是靠客户端实现的,服务端只是通过这个属性提示客户端“应该这么做”,做不做,还是决定于客户端,如果是自己宣称支持HTTP的客户端,则就应该这样实现。所以Volley肯定这样实现呀)  
  1. Cache-Control: max-age=3600  

http协议头Cache-Control :

值可以是public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age、stale-while-revalidate

各个消息中的指令含义如下:

public HTTP 响应可以由任何缓存来缓存。例如,客户端或代理服务器都可以缓存响应。这允许在使用同一代理服务器的用户之间共享内容。

private  此响应消息专门针对单个客户端,不能由共享缓存进行缓存。例如,代理服务器不应缓存响应,而客户端则可以。
这就使得一台客户端可以保留一个缓存版本,而使用同一代理服务器的其他客户端可以保留不同的缓存版本。

no-cache 告知缓存者,必须原原本本的转发原始请求,并告知任何缓存者,别直接拿你缓存的副本
no-store  语义十分强烈,请求和响应都禁止被缓存.(也许是出于隐私考虑)   
max-age 指示客户端能够使用缓存的时间(以秒为单位)
must-revalidate 与max-age配合使用,过了max-age缓存时间,就立即请求服务器数据(死刑,立即执行)
stale-while-revalidate 与max-age配合使用,过了max-age缓存时间,还可以在使用缓存(死缓,缓期就是stale-while-revalidate设置的时间。
真正使用缓存的时间是max-age + stale-while-revalidate )

ps:过了must-revalidate 缓存时间,并且没有过了stale-while-revalidate的时间,这个期间是不新鲜(refresh)的缓存。既是不新鲜也得用呀,为了不影响用户体验,请求网络是个耗时的操作。
在这个期间干了2个事,一个是把不新鲜的缓存传给用户展示。另一个肯定是请求网络获取新鲜的数据,然后把新鲜数据再缓存起来呀。。


Volley支持服务器响应的must_revalidate和stale-while-revalidate.
Expires
Expires Cache-Control 的作用一致,都是指明当前资源的 有效期 ,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。
  1. Expires: Mon, 26 Jul 1997 05:00:00 GMT
只不过Cache-Control的 选择更多,设置更细致 ,如果同时设置的话,其 优先级高于 Expires
为什么使用Expires呢?这个是个历史问题,在HTTP1.0的时候,使用Expires控制缓存时间的,这个不太灵活。。
在HTTP1.1的时候,就使用Cache-Control:max-age来控制缓存。一般为了兼容HTTP1.0和1.1,Expires和CacheControl都会加上。

Last-Modified/If-Modified-Since

Last-Modified/If-Modified-Since要配合Cache-Control使用。

Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。

Etag/If-None-Match

Etag/If-None-Match也要配合Cache-Control使用。

Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的

If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304

既生Last-Modified何生Etag?

你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304
下面是一个ETag:   
  1. ETag: "737060cd8c284d8af7ad3082f209582d"  


Location    

我们在JSP,PHP脚本中让页面Redirect到一个某个A页面中,其实是让客户端再发一个请求到A页面,这个需要Redirect到的A页面的URL,其实就是通过响应报文头的Location属性告知客户端的。

Set-Cookie      

服务端可以设置客户端的Cookie,其原理就是通过这个响应报文头属性实现的。
  1. Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1  


其它HTTP响应报文头属性      

更多其它的HTTP响应头报文,参见:   http://en.wikipedia.org/wiki/List_of_HTTP_header_fields      

响应体
关于响应体很简单,就是我们所需要的东东了,在页面上能看到的内容。比如,我们请求这个页面,json就是响应体。



2.言归正传
我们上面都是看的下HTTP报文,请求啊,响应啊~~~肯定和Volley相关呀,相信我不会把大家往沟里带的~~
下面开始解刨Volley,有些内容偷自网络,我是搬运工~~
网上也有很多不错的分析Volley源码的文章,我们这个分析基于这篇博客( http://www.codekk.com/open-source-project-analysis/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
讲的非常不错,有的内容我就不再提了,省点时间讲别的。不过重点内容还是要说的,重要的事说三遍嘛~~


来看张图吧,简单说下,有个印象
这个图很简单描述了Volley的请求流程。就是那么简单~ 理解达到一定高度就那么简单。。。

我们在使用Volley的时候,第一步就是创建一个RequestQueue。
然后就是创建Request 了。然后把Request往RequestQueue里面add~~
Volley里面提供了3中Request,StringRequest,JsonRequest,ImageRequest。
当然我们也可以自定义Request了,这里得提一下( Volley 中大多是基于接口的设计, 扩展性强, 可配置性强。

接着往下走~
我们把Request都add到RequestQueue后。。RequestQueue都干了啥? 
我们在创建一个RequestQueue后,默认会开启一个缓存线程(CacheDispatcher)和四个网络线程(NetWorkDispatcher)
CacheDispatcher负责从缓存中取数据。NetWorkDispatcher负责请求网络请求数据。
我们知道一般网络请求最好先从缓存里面取,取不到再去请求网络咯。。Volley也是那么干的咯。。

Cache就是缓存啦,Cache从什么地方取缓存数据? 就是下面的Memory,File System。。如果Cache里面没有我们请求的数据,那就只能走网络了。。
网络请求是通过HttpClientStack或HurlStack来请求Server

这个图之后,再来张图梳理下流程。。


流程我们都清楚了,然下来啃下源码吧~
我们看下,Volley所涉及到的类的关系图:

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





圈内的每个类都给说下啦,看看源码,知道数据结构:

Volley 的调用比较简单,通过 newRequestQueue(…) 函数新建并启动一个请求队列 RequestQueue后,只需要往这个 RequestQueue不断 add Request 即可。

Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue

Request:表示一个请求的抽象类。StringRequestJsonRequestImageRequest 都是它的子类,表示某种类型的请求。

RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcherNetworkDispatchers

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

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

ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。

HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack

Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse

Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的 DiskBasedCacheNetworkDispatcher得到请求结果后判断是否需要存储在 Cache, CacheDispatcher会从 Cache 中取缓存结果。

Volley的请求流程:
RequestQueue


Volley请求Demo的分析

模拟器+as
服务器地址:http://192.168.0.30/volley_server/index.php

服务器端,做了缓存(max-age),ETag/Last-Modified (为了做304响应),这些在上面都有做介绍了。。

<!DOCTYPE  html >
<html>
<body>
<?php
header( 'Cache-Control: max-age=120, public, must-revalidate' ) ; //max-age 修改为 2 分钟 , 使用 must-revalidate
//header('Cache-Control: max-age=120, public, stale-while-revalidate=60');// 使用 stale-while-revalidate
//header('Expires: '.gmdate('D, d M Y H:i:s', time() + 604800).' GMT'); // 过期时间修改为 7
header( 'Last-Modified: '  . gmdate( 'D, d M Y H:i:s' filemtime( __FILE__ )) .  ' GMT' ) // 新增此行,获取文件最后修改时间

$eTag  = md5_file( __FILE__ ) ;
$last_modified_time  = filemtime( __FILE__ ) ;

header( "Etag:  $eTag " ) ;

if (( isset ( $_SERVER [ 'HTTP_IF_NONE_MATCH' ])) && (stripslashes( $_SERVER [ 'HTTP_IF_NONE_MATCH' ]) ==  $eTag )
    ||( isset ( $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ])) && (strtotime( $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ]) ==  $last_modified_time )) {
    header( "HTTP/1.1 304 Not Modified" TRUE 304 ) ;
    exit () ;
}

echo  " 我的第一段  PHP  脚本! " ;
?>

</body>
</html>

客户端代码:

private void  requestServer2() {
    String url =  "http://192.168.0.30/volley_server/index.php" ;
    /**
     *  这里使用局部变量 request
     *  @see  NetworkDispatcher#run()  119
     *  如果服务器返回的是 304 ,第二个条件就为 false 了。。
      * 304 就是服务器不返回数据给我们,我们还是用缓存中的数据更新啦。
      */
    CustomStringRequest request =  new CustomStringRequest(url , new Response.Listener<String>() {
        @Override
        public void  onResponse(String response) {
            mResponseEdt.setText( new String(response.getBytes())) ;
        }
    } , new Response.ErrorListener() {
        @Override
        public void  onErrorResponse(VolleyError error) {
            mResponseEdt.setText( "request error") ;
        }
    }) ;
    VolleyPlayApp. getApplication().getReqQueue().add(request) ;
}

安装程序后,点击请求网络。。

从源码开始走起。。调试运行下Demo

CacheDispatcher没有搞到缓存,交给NetWorkDispatcher线程去请求网络。。
看下断点调试和打的Log吧。。
首先会执行BasicNetWork中performRequest请求网络。。
第一次请求网络,在执行addCacheHeaders的时候,request.getCacheEntry为null(因为第一次请求,之前没有搞过缓存了)
然后下面就是在线程请求网络了,得到返回的NetWorkResponse。。响应码是200。

得到NetWorkResponse ,回到NetWorkDispatcher
125行是把NetWorkResponse 解析转换成 我们需要的Response<T>

130行是把请求结果(Response)给缓存起来,为了下次使用嘛~

136标记这个请求已经得到Response
 
137就是把Response传递到主线程,给请求者啦。谁请求的就给谁。。(Request和Response是一一对应的)
在ExecutorDelivery的99行。。
mRequest调用deliverResponse方法,交给Listener的回调方法。。这样就能把Response给回传过去了。然后调用Request的finish方法。这样一次请求就完成了。


第一次请求流程图:





第二次请求~

第二次请求分三种情况:
1.如果使用缓存的话,验证缓存没有过期,CacheDispathcer 137行 
entry.refreshNeeded() 返回false。接下来就直接传回Response...

2.如果CacheDispatcher中在找到缓存的时候,运行到135行,如果这时候正好缓存过期了或者是不新鲜了(这个是两码事,有点复杂),是不是该再验证一遍?
就在137行验证了。。为了提升用户的体验度,过期了不能再去请求网络做耗时的操作吧,还是应急一下,得把缓存的东东拿出来先交给主线程使用。
148行,把response.intermediate设置为true。
然后这个线程再去默默请求网络,得到响应后,把请求结果默默给缓存起来。再把最新的数据传递给Request,然后给刷新展现出来。
(主要意思就是先使用缓存,请求到网络数据后,然后再使用网络数据)
回到ExecutorDelivery 106行,就该判断这个Response.intermediate吧。。这个响应只是个应急措施,不能算是一次请求的完成。就不会调用Request的finish方法。

3.CacheDispatcher中的123行。。判断缓存是否过期。过期了,也要把缓存(entry)设置到request,后面是有用滴~
然后把这个请求放到网络请求队列里面。肯定走网络请求。。

我们看下第二次请求网络BasicNetWork的performRequest...
然后是addCacheheaders添加请求头。这是第二次请求了吧,这样就会带上If-None-Match,If-Modified-Since(这两个信息要提交到服务器,服务器拿到这俩请求头信息,会做处理。)
-------------------------------------------------
Demo预期效果:
第二次请求服务器,根据服务器脚本,可知会返回304~

清理下客户端数据,然后再请求第一次是200,在缓存时间之内都是200(在CacheDispatcher 133行,NetWorkResponse的构造就是SC_OK),
过了缓存期再次请求就是304,这样又来了一次新的缓存,接下来都是200~~ 过期再请求就是304 。。。。循环。。
304用的是缓存。200用的也是缓存。。一直在充分利用缓存。。这样流量是不是少了很多~而且加载的特别快。。
-------------------------------------------------
第二次请求流程图:


 
缓存线程和网络线程总的执行过程如下: 


CacheDispatcher 缓存线程优先



然后是网络请求线程NetworkDispatcher
 



到最后,请求完成,会调用Request的finish方法。。

finish这个方法不仅在请求完成时被调用,也在被取消时候调用啦。
都会走这个方法。逻辑都是一样的。只是往finish方法里传入的字符参数不一样而已了。

void  finish( final String tag) {
    if ( mRequestQueue !=  null) {
        mRequestQueue.finish( this) ;
        onFinish() ;
    }
    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()) ;
    }
}

主要逻辑就是调用RequestQueue的finish方法。

< Tvoid  finish(Request< T> request) {
    // Remove from the set of requests currently being processed.
    synchronized ( mCurrentRequests) {
        mCurrentRequests.remove(request) ;
    }
    synchronized ( mFinishedListeners) {
      for (RequestFinishedListener< T> listener :  mFinishedListeners) {
        listener.onRequestFinished(request) ;
      }
    }

    if (request.shouldCache()) {
        synchronized ( mWaitingRequests) {
            String cacheKey = request.getCacheKey() ;
            Queue<Request<?>> waitingRequests =  mWaitingRequests.remove(cacheKey) ;
            if (waitingRequests !=  null) {
                if (VolleyLog. DEBUG) {
                    VolleyLog. v( "Releasing %d waiting requests for cacheKey=%s." ,
                            waitingRequests.size() cacheKey) ;
                }
                // Process all queued up requests. They won't be considered as in flight, but
                // that's not a problem as the cache has been primed by 'request'.
                mCacheQueue.addAll(waitingRequests) ;
            }
        }
    }
}

第一就是把这个请求从当前的请求队列给移除吧,然后286行。。
判断request是不是应该缓存的?每个request默认是缓存的,这个条件肯定通过
然后是从 请求等待队列集合取出找出相同URL+Method的 请求队列,然后添加到缓存队列~~继续让CacheDispatcher爱咋处理咋处理咯。。
这个做法很聪明的。。因为相同的URL+Method就是相同的请求,之前有相同请求了,就有缓存了。只要缓存没过期,就可以拿来用。
这样每个Request都没必要再去请求了。服务器的压力也小了。。

如果我们服务器不支持缓存的话,如何做,之前也分享过了。。

import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.toolbox.HttpHeaderParser;

import org.apache.http.impl.cookie.DateUtils;

import java.util.Date;
import java.util.Map;

/**
 * Created by congwiny on 2015/12/3.
 */
public class CustomHttpHeaderParser extends HttpHeaderParser {

    public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long lastModified = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        long maxAge = 0;
        long staleWhileRevalidate = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;

        String serverEtag = null;
        String headerValue;

        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }
        //造假头信息
        headers.put("Cache-Control", "max-age=86400");//缓存24小时
        long time = serverDate + 1000 * 60 * 60 * 24;
        Date expiresTime = new Date(time);
        headers.put("Expires", DateUtils.formatDate(expiresTime, DateUtils.PATTERN_RFC1123));

        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();
                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.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }

        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            lastModified = 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;
            finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }

        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = finalExpire;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;
        return entry;
    }
}



总结:使用Volley,前端+后端  好好配合能大大减少流量,提升访问速度,减少服务器压力。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值