web-dev-qa-db-fra.com

Android Volley + Mise en cache JSONObjectRequest

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);
    }
}

J'espérais que ce code me suffirait pour obtenir la mise en cache implicite des réponses. Je ne sais pas si cela fonctionne ou non, car j'étais sous l'hypothèse quand une demande est envoyée:

  1. il frapperait d'abord le cache et l'enverrait à onresponse

  2. puis, lorsque les résultats sont transmis par le serveur distant, ils le fournissent à la réponse 

Mettre à jour:

J'ai imaginé comment récupérer manuellement le cache et le reconstruire en un objet JSONObject et l'envoyer via la fonction OnResponse, mais cela ne semble pas efficace compte tenu de la mise en cache implicite. La classe JsonObjectRequest doit renvoyer JSONObject comme entrée mise en cache au lieu des données de réponse brutes.

Mais je suis toujours intéressé de savoir si je fais une erreur.

L'ambiguïté est uniquement due au manque de documentation, alors je m'excuse si quelque chose d'assez évident me manque.

48
gaara87

Voir cette réponse - Définir la politique d’expiration pour le cache à l’aide de Google Volley

Cela signifie que Volley décide de mettre en cache ou non la réponse en fonction des en-têtes "Cache-Control" puis "Expires", "maxAge".

Ce que vous pouvez faire, c'est modifier cette méthode com.Android.volley.toolbox.HttpHeaderParser.parseCacheHeaders(NetworkResponse response) Et ignorer ces en-têtes, définir les champs entry.softTtl et entry.ttl à la valeur qui vous convient et utiliser votre méthode dans votre classe de demande. Voici un exemple:

/**
 * 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;
}

Utilisez cette méthode dans votre classe Request comme ceci:

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));
    }

}
87

oleksandr_yefremov fournit d'excellents codes qui peuvent vous aider lorsque vous traitez avec la stratégie de cache d'Android Volley, en particulier lorsque l'API REST possède des en-têtes "Cache-Control" incorrects ou si vous souhaitez simplement davantage de contrôle sur votre propre stratégie de cache d'applications.

La clé est HttpHeaderParser.parseCacheHeaders(NetworkResponse response)). Si vous voulez avoir votre propre stratégie de cache. Remplacez-le par parseIgnoreCacheHeaders(NetworkResponse response) dans la classe correspondante

Si votre classe étend JsonObjectRequest, accédez à JsonObjectRequest et recherchez 

@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));
        }
}

et remplacez HttpHeaderParser.parseCacheHeaders(response) par HttpHeaderParser.parseIgnoreCacheHeaders

5
skyfishjy

+1 pour oleksandr_yefremov et skyfishjy également, et offre ici une classe concrète réutilisable adaptée à json ou à d'autres API basées sur des chaînes:

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));
    }
}

où la fonction parseIgnoreCacheHeaders () provient de la réponse oleksandr_yefremov ci-dessus. Utilisez la classe CachingStringRequest n'importe où que le fichier json résultant puisse être mis en cache pendant 3 minutes (en direct) et 24 heures (expiré mais toujours disponible). Un exemple de demande:

CachingStringRequest stringRequest = new CachingStringRequest(MY_API_URL, callback);

et dans la fonction onResponse () de l'objet de rappel, analysez le JSON. Définissez les limites de mise en cache que vous souhaitez - vous pouvez paramétrer pour ajouter une expiration personnalisée par demande.

Pour vous amuser, essayez ceci dans une application simple qui télécharge json et restitue les informations téléchargées. Après avoir rempli le cache avec le premier téléchargement réussi, observez le rendu rapide lorsque vous modifiez les orientations pendant que le cache est actif (aucun téléchargement ne se produit si un accès au cache est actif). Maintenant, supprimez l’application, attendez 3 minutes que le cache soit expiré (mais pas dans les 24 heures qui suivent pour le supprimer), activez le mode avion et redémarrez l’application. Le rappel d'erreur Volley se produira ET le rappel "abouti" onResponse () se produira à partir de données en cache, ce qui permettra à votre application de restituer le contenu et de savoir/avertir qu'il provient d'un cache expiré. 

Une utilisation de ce type de mise en cache consisterait à éviter les chargeurs et autres moyens de gérer le changement d’orientation. Si une requête passe par un singleton Volley et que les résultats sont mis en cache, les actualisations effectuées lors d'un changement d'orientation sont rapidement rendues à partir du cache, automatiquement par Volley, sans le chargeur. 

Bien sûr, cela ne correspond pas à toutes les exigences. YMMV

2
larham1

J'ai pu forcer Volley à mettre en cache toutes les réponses en étendant StringRequest et en remplaçant la demande que je veux forcer en cache par CachingStringRequest.

Dans la méthode surchargée parseNetworkResponse, je supprime les en-têtes Cache-Control. De cette façon, Volley conserve la réponse dans son cache intégré.

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);
    }
}
0
sakydpozrux