Android Volley + JSONObjectRequest 缓存

50
public class CustomRequest extends JsonObjectRequest {

    public CustomRequest(String url, JSONObject params,
            Listener<JSONObject> listener, ErrorListener errorListener)
            throws JSONException {
        super(Method.POST,url, params, listener,
                errorListener);
        this.setShouldCache(Boolean.TRUE);
    }
}

我希望这段代码足以实现响应的隐式缓存,但我不确定它是否起作用。我假设当发送请求时:

  1. 会首先命中缓存并将其发送到 OnResponse 中

  2. 然后当来自远程服务器的结果到达时,会将其提供给 OnResponse

更新:

我已经找到了手动检索缓存并将其重构为 JSONObject 并通过 OnResponse 函数发送的方法,但考虑到存在隐式缓存,这种方法似乎不太高效。 JsonObjectRequest 类应该返回JSONObject作为缓存条目,而不是原始响应数据。

但我仍然想知道我是否犯了一些错误。

由于缺乏文档而导致的歧义,因此如果我错过了一些非常明显的东西,我深表歉意。

4个回答

89

请参考这个回答 - 使用Google的Volley设置缓存过期策略

这意味着Volley仅基于“Cache-Control”以及“Expires”和“maxAge”标头来决定是否缓存响应。

您可以更改此方法com.android.volley.toolbox.HttpHeaderParser.parseCacheHeaders(NetworkResponse response)并忽略这些标头,将entry.softTtlentry.ttl字段设置为适合您的任何值,并在您的请求类中使用您的方法。以下是一个示例:

/**
 * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}.
 * Cache-control headers are ignored. SoftTtl == 3 mins, ttl == 24 hours.
 * @param response The network response to parse headers from
 * @return a cache entry for the given response, or null if the response is not cacheable.
 */
public static Cache.Entry parseIgnoreCacheHeaders(NetworkResponse response) {
    long now = System.currentTimeMillis();

    Map<String, String> headers = response.headers;
    long serverDate = 0;
    String serverEtag = null;
    String headerValue;

    headerValue = headers.get("Date");
    if (headerValue != null) {
        serverDate = HttpHeaderParser.parseDateAsEpoch(headerValue);
    }

    serverEtag = headers.get("ETag");

    final long cacheHitButRefreshed = 3 * 60 * 1000; // in 3 minutes cache will be hit, but also refreshed on background
    final long cacheExpired = 24 * 60 * 60 * 1000; // in 24 hours this cache entry expires completely
    final long softExpire = now + cacheHitButRefreshed;
    final long ttl = now + cacheExpired;

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

    return entry;
}

像这样在您的Request类中使用此方法:

public class MyRequest extends com.android.volley.Request<MyResponse> {

    ...

    @Override
    protected Response<MyResponse> parseNetworkResponse(NetworkResponse response) {
        String jsonString = new String(response.data);
        MyResponse MyResponse = gson.fromJson(jsonString, MyResponse.class);
        return Response.success(MyResponse, HttpHeaderParser.parseIgnoreCacheHeaders(response));
    }

}

1
这是否意味着缓存将持续到 onDestroy 之后?这样下一次创建应用程序时,它将从缓存中获取? - gaara87
3
是的,缓存不仅保存在内存中,还保存在磁盘上(详见DiskBasedCache类)。作为一个快速测试,加载一些数据,退出应用程序,关闭wifi或3g,然后再次进入应用程序。您也可以在mMaxCacheSizeInBytes字段中指定缓存大小。 - Oleksandr Yefremov
2
是的,当我在应用程序内时,它会缓存,但是当我退出应用程序并重新进入应用程序时,从缓存获取返回 null。因此,问题是它是否存在于活动生命周期之间。 - gaara87
1
这很有用,但它仍然只缓存原始响应,而不是解析后的响应。因此,即使在缓存命中的情况下,您也必须重新解析响应。对于非平凡的JSON有效载荷,即使对于缓存的响应,这也可能增加显着的负载时间。正如Volley日志所示:(+177 ) [687] cache-hit-parsed177毫秒用于重新解析响应。 - Brett Duncavage
2
我正在做同样的事情,但它没有清除缓存。过期时间为1分钟。 - JosephM
显示剩余8条评论

5

oleksandr_yefremov提供了很棒的代码,可以帮助你处理Android Volley的缓存策略,特别是当REST API具有不当的"Cache-Control"头或者你只是想更好地控制你自己的应用程序缓存策略时。

关键是HttpHeaderParser.parseCacheHeaders(NetworkResponse response))。如果你想拥有自己的缓存策略,请在相应的类中将其替换为parseIgnoreCacheHeaders(NetworkResponse response)

如果你的类扩展了JsonObjectRequest,请转到JsonObjectRequest并查找

@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
    try {
            String jsonString =new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(new JSONObject(jsonString),HttpHeaderParser.parseCacheHeaders(response));
        }catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
}

HttpHeaderParser.parseCacheHeaders(response)替换为HttpHeaderParser.parseIgnoreCacheHeaders


是否有可能使用 NetworkImageView 来实现这个功能? - sdabet

2

对oleksandr_yefremov和skyfishjy点赞,并在此提供一个具体的、可重复使用的类,适用于JSON或其他基于字符串的API:

public class CachingStringRequest extends StringRequest {
    public CachingStringRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        super(method, url, listener, errorListener);
    }

    public CachingStringRequest(String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        super(url, listener, errorListener);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, parseIgnoreCacheHeaders(response));
    }
}

函数parseIgnoreCacheHeaders()来源于上面oleksandr_yefremov的答案。在任何结果json可以缓存3分钟(实时)和24小时(过期但仍可用)的地方使用CachingStringRequest类。一个示例请求:

CachingStringRequest stringRequest = new CachingStringRequest(MY_API_URL, callback);

在回调对象的onResponse()函数内解析json。设置任何缓存限制,你可以参数化以添加每个请求的自定义到期时间。尝试在一个简单的应用程序中下载json并呈现已下载信息,填充缓存与第一次成功下载,观察当缓存处于活动状态时更改方向的快速渲染(给定活动缓存命中不会发生下载)。现在杀死应用程序,等待3分钟使缓存命中过期(但不是24小时从缓存中删除),启用飞行模式并重新启动应用程序。Volley错误回调将发生,并且来自缓存数据的"成功"onResponse()回调也将发生,让你的应用程序能够呈现内容并知道/警告它来自过期的缓存。这种缓存的一种用途是消除加载器和处理方向更改的其他手段。如果一个请求通过Volley单例进行,结果被缓存,那么通过方向更改进行的刷新将由Volley自动快速地从缓存中呈现,而无需使用加载器。当然,这并不适合所有要求。你的情况可能有所不同。

0
我通过扩展StringRequest并将要强制缓存的请求替换为CachingStringRequest,成功地强制Volley缓存所有响应。
在重写的方法parseNetworkResponse中,我删除了Cache-Control头。这样,Volley会将响应持久化到其内置缓存中。
public class CachingStringRequest extends StringRequest {
    private static final String CACHE_CONTROL = "Cache-Control";

    public CachingStringRequest(int method,
                                String url,
                                Response.Listener<String> listener,
                                Response.ErrorListener errorListener) {
        super(method, url, listener, errorListener);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        // I do this to ignore "no-cache" headers
        // and use built-in cache from Volley.
        if (response.headers.containsKey(CACHE_CONTROL)) {
            response.headers.remove(CACHE_CONTROL);
        }

        return super.parseNetworkResponse(response);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接