[Android]Volley在没有网络时读取图片缓存时出现的问题

Volley框架实现了L2缓存,却没有实现L1缓存。

我们团队遇到的问题是

当imgurl为类似于“http://www.XXX,com/XXXX/XXXX.png”时能完成加载

当imgurl为“http://192.168.XXX.XXX:8080/XXX/XXX.png”时总是加载错误

经过一周的排查发现

只要在

服务器的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的源码真值得反复看,琢磨,模仿。

不服

自己看源码,下面列源码

在扫过上述别人的分析后,对自己的问题的关键有所了解

我决定:

1.看Volley是否实现L2缓存;

2.什么时候存储L2缓存;

3.L2储存成功后是否使用

问题1

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(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) {
            ;
        }
        //省略代码
        //这个创建了L2缓存文件夹
        RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);

        return queue1;
    }

可以看见,在调用

Volley.newRequestQueue(Context)

创建请求队列的时候,创建了L2缓存

问题2

 public void start() {
        this.stop();
        this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
        this.mCacheDispatcher.start();

        for(int i = 0; i < this.mDispatchers.length; ++i) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
            this.mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }

    }

启动队列,关注CacheDispatcher和NetworkDispatcher

思考,第一次(没有任何缓存状态),应该只有NetworkDispatcher起作用

因此只关注NetworkDispatcher(是线程)

    public void run() {
        //好多好乱?慢慢看
        Process.setThreadPriority(10);
       //不断在请求队列取请求,忽略
        while(true) {
            Request request;
            while(true) {
                try {
                    request = (Request)this.mQueue.take();
                    break;
                } catch (InterruptedException var4) {
                    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回复,重点看
                    NetworkResponse e = this.mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
                    if(e.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                    } else {
                       //转型成Volley的Response类型,重点看
                        Response response = 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 (VolleyError var5) {
                this.parseAndDeliverNetworkError(request, var5);
            } catch (Exception var6) {
                VolleyLog.e(var6, "Unhandled exception %s", new Object[]{var6.toString()});
                this.mDelivery.postError(request, new VolleyError(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!

结合上面那个网址,结合ttl!学了计算机那么久总能猜到是缓存的生命周期(time to live)!

——————————————楔子——————————————————

这个问题我们团队大概花了一周解决

期间虽然进度拖慢了但收获颇丰

期间也有同学提出用其他图片缓存框架,但是这种饮鸩止渴的方式怎么能用在程序圆身上

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-05 19:40:59

[Android]Volley在没有网络时读取图片缓存时出现的问题的相关文章

Android Volley 库通过网络获取 JSON 数据

本文内容 什么是 Volley 库 Volley 能做什么 Volley 架构 演示 Volley 库通过网络获取 JSON 数据 参考资料 Android 关于网络操作一般都会介绍 HttpClient 以及 HttpConnection 这两个包.前者是 Apache 开源库,后者是 Android 自带 API.企业级应用,一般都会选择使用已经封装好的 http 框架.比较流行有 Volley.android-async-http.retrofit.okhttp.androidquery.

python 从网络URL读取图片并直接处理的代码

如下代码段是关于python 从网络URL读取图片并直接处理的代码. import urllib2 import Image import cStringIO def ImageScale(url,size): file = cStringIO.StringIO(urllib2.urlopen(url).read()) img = Image.open(file) img.show() 原文地址:http://blog.51cto.com/14142860/2347335

移动应用开发(IOS/android等)中一个通用的图片缓存方案讲解(附流程图)

在移动应用开发中,我们经常会遇到从网络请求图片到设备上展示的场景. 如果每次都重复发起请求,浪费流量.浪费电量,用户体验也不佳: 将图片持久化到磁盘也不失为一种策略:但每次从文件读取图片也存在一定的io开销,就算采用此策略,我们也需要控制磁盘缓存的容量,以免占用过多系统资源. 其实没有一个方案可以说是完美的方案,只有最适合自己业务需求的方案,才可以说是一个好方案. 我们下面所讲解的方案具备很强的通用性,设计思路简单而清晰: 1.假设每个网络图片的url具有唯一性,如果网络上的图片变化了,会引起输

【iOS】网络加载图片缓存与SDWebImage

加载网络图片可以说是网络应用中必备的.如果单纯的去下载图片,而不去做多线程.缓存等技术去优化,加载图片时的效果与用户体验就会很差. 一.自己实现加载图片的方法 tips: *iOS中所有网络访问都是异步的.(自己开线程去下载) *普通为模型增加UIImage属性的方法做的是内存缓存(下次启动还需要从网络重新加载), 而要做本地缓存的话,还要自己手动存储网络上下载的图片. *为了加快访问, 还需要自己去弄缓存.(内存缓存或者本地缓存) *当图片没有下载完成时,还要设置占位图片. 以下代码用NSOp

原 Volley框架之网络请求和图片加载

Volley是 Google 推出的 Android 异步网络请求框架和图片加载框架. Volley的特性 (1).封装了的异步的请求API.Volley 中大多是基于接口的设计,可配置性强.(2).一个优雅和稳健的请求队列,一定程度符合 Http 规范,包括请求头的处理,缓存机制的支持等.(3).自定义的网络图像加载视图(NetworkImageView,ImageLoader等) .(4). 提供简便的图片加载工具. 本案例包含get,post请求和几种网络图片加载的方式,效果如图:   

035 Android Volley框架进行网络请求

1.volley入门介绍 开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高. Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行

[Android]通过setImageURI设置网络上面的图片

设置imageView显示网络上的图片 picUrl = new URL(getIntent().getExtras().getString("map_url")); Bitmap pngBM = BitmapFactory.decodeStream(picUrl.openStream()); mapIMG.setImageBitmap(pngBM);

LruCache:从网络加载图片缓存实例

OOM异常 堆内存用于存储实例对象,当程序不断创建对象,并且对象都有引用指向,那么垃圾回收机制就不会清理这些对象,当对象多到挤满堆内存的上限后,就产生OOM异常.Android系统为每个应用程序使用的内存设置了一个上限.这个上限值可以用下面的方法取得: long maxSize = Runtime.getRuntime().maxMemory(); OOM异常通常分为下面几种情况:1.内存泄漏导致的OOM:new出来的很多对象已经不需要了,但仍然有引用指向,所以垃圾回收机制无法回收. 其场景类似

Web Service 或 WCF调用时读取 XML 数据时,超出最大字符串内容长度配额(8192)解决方法

1.调用服务时服务 当我们使用 Web Service 或 WCF 服务时,常把读取的数据转化为string类型(xml格式),当数据量达到一 定数量时,会出现以下异常: 错误:格式化程序尝试对消息反序列化时引发异常: 尝试对参数 http://tempuri.org/ (命名空间)进行反序列化时出错: InnerException 消息是“反序列化对象异常,读取 XML 数据时,超出最大字符串内容长度配额 (8192).通过更改在创建 XML 读取器时所使用的 XmlDictionaryRea