Android Volley + JSONObjectRequest Caching
See this answer - Set expiration policy for cache using Google's Volley
This means Volley decides whether to cache response or not based only on headers "Cache-Control" and then "Expires", "maxAge".
What you could do is change this method
com.android.volley.toolbox.HttpHeaderParser.parseCacheHeaders(NetworkResponse response)
and ignore these headers, set entry.softTtl
and entry.ttl
fields to whatever value works for you and use your method in your request class. Here is an example:
/**
* 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;
}
Use this method in your Request class like this:
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));
}
}
oleksandr_yefremov provides great codes that can help you when you dealing with cache strategy of Android Volley, especially when the REST API has improper "Cache-Control" headers or you just want more control on your own app cache strategy.
The key is HttpHeaderParser.parseCacheHeaders(NetworkResponse response))
. If you want to have your own cache strategy. Replace it with parseIgnoreCacheHeaders(NetworkResponse response)
in corresponding class.
If your class extends JsonObjectRequest, go to JsonObjectRequest and find
@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));
}
}
and replace HttpHeaderParser.parseCacheHeaders(response)
with HttpHeaderParser.parseIgnoreCacheHeaders
+1 for oleksandr_yefremov and skyfishjy also, and offering here a concrete, reusable class suitable for json or other string-based APIs:
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));
}
}
where the function parseIgnoreCacheHeaders() comes from the oleksandr_yefremov answer above. Use the CachingStringRequest class anywhere that the resulting json is ok to cache for 3 minutes (live) and 24 hours (expired but still available). A sample request:
CachingStringRequest stringRequest = new CachingStringRequest(MY_API_URL, callback);
and within the callback object's onResponse() function, parse the json. Set whatever caching limits you want--you could parameterize to add custom expiration per request.
For fun, try this in a simple app that downloads json and renders the downloaded info. Having filled the cache with the first successful download, watch the speedy rendering as you change orientations while cache is live (no download occurs given a live cache hit). Now kill the app, wait 3 minutes for that cache hit to expire (but not 24 hours for it to be removed from cache), enable airplane mode, and restart the app. The Volley error callback will occur, AND the "successful" onResponse() callback will occur from cached data, allowing your app to both render content and also know/warn that it came from expired cache.
One use of this kind of caching would be to obviate Loaders and other means of dealing with orientation change. If a request goes through a Volley singleton, and results are cached, refreshes that happen via orientation change are rendered quickly from cache, automatically by Volley, without the Loader.
Of course, this doesn't fit all requirements. YMMV