web-dev-qa-db-ja.com

Android Volley + JSONObjectRequest Caching

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をキャッシュされたエントリとして返す必要があります。

しかし、私はまだ間違いを犯しているかどうかを知りたいと思っています。

あいまいさは、単にドキュメントが不足しているためです。そのため、非常に明白なものが不足している場合は謝罪します。

49
gaara87

この回答を参照してください- GoogleのVolleyを使用してキャッシュの有効期限ポリシーを設定する

つまり、Volleyは、ヘッダー「Cache-Control」、次に「Expires」、「maxAge」のみに基づいて、応答をキャッシュするかどうかを決定します。

できることは、このメソッドcom.Android.volley.toolbox.HttpHeaderParser.parseCacheHeaders(NetworkResponse response)を変更し、これらのヘッダーを無視し、entry.softTtlおよびentry.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));
    }

}
88

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_に置き換えます

5
skyfishjy

oleksandr_yefremovとskyfishjyの+1もあり、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()コールバックが発生します。これにより、アプリはコンテンツをレンダリングし、キャッシュが期限切れであることを認識/警告できます。

この種のキャッシングの使用法の1つは、ローダーやその他の方向の変更に対処する手段を不要にすることです。リクエストがVolleyシングルトンを通過し、結果がキャッシュされる場合、方向の変更によって発生する更新は、ローダーなしでVolleyによって自動的にキャッシュから迅速にレンダリングされます。

もちろん、これはすべての要件に適合するわけではありません。 YMMV

2
larham1

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