Volley读取L2缓存时出现的问题

问 没有网络的情况下,Volley的L2(硬盘)缓存不起作用
答案 ****服务器的Response头中加入'cache-control:public, max-age=43200'(只是示例)****
原理 简单来说,Volley框架考虑的东西比较全面,终端(手机或浏览器)的缓存策略是由服务器来制定的,而Volley在做缓存之前会判断服务器是否允许自己做这个缓存。
服务器缓存在缺省情况下,是不让终端做缓存的
更多关于缓存的资料,请移步
HTTP 缓存 — Web Fundamentals
求解过程 这个问题我们团队之前也有遇到,Google得到的信息是Volley已实现L2缓存(基于硬盘),需要自己实现L1缓存(基于内存)。既然L2缓存已经实现,那没网的情况下是可以加载图片的。有个小伙伴发现,用不同的url会影响L2缓存的实现。
看源码,先看别人写过的源码解析
Volley 源码解析
Android 网络通信框架Volley简介(Google IO 2013)
Android Volley完全解析(一),初识Volley的基本用法
https://developer.android.com/training/volley/index.html
.....
以上花了大概两三天都扫一遍
除了感慨Volley写得游刃有余之外,并没有太大帮助。
建议Volley的源码真值得反复看,琢磨,模仿。
自己看源码,下面剖析源码,检查下面的问题

  • 看Volley是否实现L2缓存;
  • 什么时候存储L2缓存;
  • L2储存成功后是否使用
问题1
public staticRequestQueue newRequestQueue(Context context,HttpStack stack) { File cacheDir =newFile(context.getCacheDir(),"volley"); String userAgent ="volley/0"; try{ String network = context.getPackageName(); PackageInfo queue = context.getPackageManager().getPackageInfo(network,0); userAgent = network +"/"+ queue.versionCode; }catch(NameNotFoundException var6) {} if(stack ==null) { if(VERSION.SDK_INT >=9) { stack =newHurlStack(); }else{ stack =newHttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } BasicNetwork network1 =newBasicNetwork((HttpStack)stack); RequestQueue queue1 =newRequestQueue(newDiskBasedCache(cacheDir),network1); queue1.start(); returnqueue1; }

可以看见,在调用
Volley.newRequestQueue(Context)
创建请求队列的时候,创建了L2缓存
问题2
public void start() { this.stop(); this.mCacheDispatcher = newCacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery); this.mCacheDispatcher.start(); }

启动队列,关注CacheDispatcher和NetworkDispatcher
思考,第一次(没有任何缓存状态),应该只有NetworkDispatcher起作用
因此只关注NetworkDispatcher(是线程)
public void run() { Process.setThreadPriority(10); //不断在请求队列取请求,忽略 while (true) { Requestrequest; while (true) { try { request = (Request) this.mQueue.take(); break; } catch (InterruptedExceptionvar4) { if (this.mQuit) { return; } } } //检查是否被取消,忽略 try { request.addMarker("network-queue-take"); if (request.isCanceled()) { request.finish("network-discard-cancelled"); } else { if (VERSION.SDK_INT >= 14) { TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); } //得到Response回复,重点看NetworkResponsee=this.mNetwork.performRequest(request); request.addMarker("network-http-complete"); if(e.notModified&&request.hasHadResponseDelivered()){request.finish("not-modified"); }else{ //转型成Volley的Response类型,重点看Responseresponse=request.parseNetworkResponse(e); request.addMarker("network-parse-complete"); if (request.shouldCache() && response.cacheEntry != null) { this.mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } request.markDelivered(); this.mDelivery.postResponse(request, response); } } }catch(VolleyErrorvar5) {this.parseAndDeliverNetworkError(request, var5); }catch(Exceptionvar6) {VolleyLog.e(var6, "Unhandled exception %s", newObject[]{ var6.toString() }); this.mDelivery.postError(request, newVolleyError(var6)); }}

原来重点在request.shouldCache()l&&response.cacheEntry != nul判断是否缓存!
继续点进去
public final boolean shouldCache() { return this.mShouldCache; }

可以在构造方法里看出
public Request(int method, String url, ErrorListener listener) { this.mEventLog = MarkerLog.ENABLED?new MarkerLog():null; this.mShouldCache = true; //注意这里 this.mCanceled = false; this.mResponseDelivered = false; this.mRequestBirthTime = 0L; this.mCacheEntry = null; this.mMethod = method; this.mUrl = url; this.mErrorListener = listener; this.setRetryPolicy(new DefaultRetryPolicy()); this.mDefaultTrafficStatsTag = TextUtils.isEmpty(url)?0:Uri.parse(url).getHost().hashCode(); }

cacheEntry 是继承Request时需要重写的,例如StringRequest
parsed = new String(response.data,HttpHeaderParser.parseCharset(response.headers));

好!这个结果也是预料之中。
因为使用Android模拟器的时候,也是可以在/data/data/包名/volley/...目录下有图片文件生成
继续看调用缓存的时候发生了什么。
问题3 代码有点多,直接贴最关键的
final Request e = (Request)this.mCacheQueue.take(); e.addMarker("cache-queue-take"); if(e.isCanceled()) { e.finish("cache-discard-canceled"); } else { Entry entry = this.mCache.get(e.getCacheKey()); if(entry == null) { e.addMarker("cache-miss"); this.mNetworkQueue.put(e); //这里! } else if(entry.isExpired()) { e.addMarker("cache-hit-expired"); e.setCacheEntry(entry); this.mNetworkQueue.put(e); } //省略部分代码....

L2缓存是通过请求url作为cache-key来储存的。
所以entry.isExpired()是重点!
如果entry.isExpired()为true,则返回缓存,而false则进入网络请求队列继续网络请求
public boolean isExpired() { return this.ttl < System.currentTimeMillis(); }

ttl!ttl!ttl!
【Volley读取L2缓存时出现的问题】结合上面那个网址,结合ttl!学了计算机那么久总能猜到是缓存的生命周期(time to live)!
——————————————楔子——————————————————
这个问题我们团队大概花了一周解决
期间虽然进度拖慢了但收获颇丰
期间也有同学提出用其他图片缓存框架,但是这种饮鸩止渴的方式怎么可

    推荐阅读