web-dev-qa-db-ja.com

Android=でOkHttpのキャッシュを設定する正しい方法

私はOkHttpのキャッシュをセットアップしようとしています。そのため、有効期限のヘッダーの日付までサーバーから応答を取得しようとする最初のときのみサーバーに要求します。または、サーバーからのキャッシュ制御ヘッダーが応答を無効にしますキャッシュから。

現在、それは応答をキャッシュしますが、リソースを再度要求するときに使用しません。これは、本来の使用方法ではない可能性があります。

私はこのようなキャッシュでOkHttpClientを設定しています:

public static Cache createHttpClientCache(Context context) {
    try {
        File cacheDir = context.getDir("service_api_cache", Context.MODE_PRIVATE);
        return new Cache(cacheDir, HTTP_CACHE_SIZE); 
    } catch (IOException e) {
        Log.e(TAG, "Couldn't create http cache because of IO problem.", e);
        return null;
    }
}

これは次のように使用されます:

if(cache == null) {
    cache = createHttpClientCache(context);
}
sClient.setCache(cache);

これは、実際にキャッシュの使用に失敗しているOkHttpを使用してサーバーにリクエストを送信する方法です。

public static JSONObject getApi(Context context) 
        throws IOException, JSONException, InvalidCookie {

    HttpCookie sessionCookie = getServerSession(context);
    if(sessionCookie == null){
        throw new InvalidCookie();
    }
    String cookieStr = sessionCookie.getName()+"="+sessionCookie.getValue();

    Request request = new Request.Builder()
        .url(sServiceRootUrl + "/api/"+API_VERSION)
        .header("Accept", "application/json")
        .header("Cookie", cookieStr)
        .build();

    Response response = sClient.newCall(request).execute();
    if(response.code() == 200){
        String charset = getResponseCharset(response);
        if(charset == null){
            charset = "utf-8";
        }
        String responseStr = new String(response.body().bytes(), charset);
        response.body().close();
        return new JSONObject(responseStr);
    } else if(response.code() == 401){
        throw new InvalidCookie();
    } else {
        return null;
    }

}

OkHttpのキャッシュとして指定したディレクトリにアクセスすると、ジャーナルファイルと、いくつかの要求に対する応答を含む他の4つのファイルが表示されます。このリクエスト(コードを貼り付けた/ apiの1つ)はキャッシュディレクトリに保存されるため、実際にキャッシュされましたが、ファイル名の最後に.tmpがあり、最終ファイルに適切に保存されなかったかのように、他のリクエストと同じように。

これは、リクエストに対するサーバー応答のヘッダーのようになります。

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Expires: Sat, 09 Aug 2014 19:36:08 GMT
Cache-Control: max-age=86400, must-revalidate
Last-Modified: Sun, 04 Aug 2013 15:56:04 GMT
Content-Length: 281
Date: Fri, 08 Aug 2014 19:36:08 GMT

そして、これはOkHttpがそれをキャッシュに保存する方法です:

{Host}/api/0.3
GET
0
HTTP/1.1 200 OK
9
Server: Apache-Coyote/1.1
Expires: Sat, 09 Aug 2014 19:36:08 GMT
Cache-Control: max-age=86400, must-revalidate
Last-Modified: Sun, 04 Aug 2013 15:56:04 GMT
Content-Length: 281
Date: Fri, 08 Aug 2014 19:36:08 GMT
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1407526495630
OkHttp-Received-Millis: 1407526495721

OkHttpがこのファイルを作成した後、サーバーに同じリソースを要求し続けます。これらのメッセージはWiresharkで確認できます。

何が悪いのですか?

更新:

これは、Jesseの提案後のサーバー応答です。

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Expires: Thu, 14 Aug 2014 18:06:05 GMT
Last-Modified: Sun, 10 Aug 2014 12:37:06 GMT
Content-Length: 281
Date: Wed, 13 Aug 2014 18:06:05 GMT

更新2:コードのバージョンを試してみたところ、キャッシュに関するどこかにバグがある可能性が非常に高いことがわかりました。これは私がMaven出力から得たものです:

Results :

Failed tests: 
  CacheTest.conditionalHitUpdatesCache:1653 expected:<[A]> but was:<[B]>

Tests in error: 
  CallTest.tearDown:86 » IO failed to delete file: C:\Users\Adrian\AppData\Local...

Tests run: 825, Failures: 1, Errors: 1, Skipped: 17

より完全なログはここで見ることができます: https://Gist.github.com/16BITBoy/344ea4c22b543f397f5

16
Adrián Pérez

私は問題を解決しました。ソースからOkHttpを使用しようとすると、キャッシュが失敗することをテストするのは少し誤解を招きました。

問題は非常に簡単で、他の要求メソッドが応答の本文を取得していて、最後に閉じられなかったことです。これが、キャッシュ内の ".tmp"ファイルを見た理由ですが、このリクエストメソッドが消費してレスポンスの本文を閉じていたため、混乱して誤解を招きます。キャッシュエディターのロックまたはモニターと同様に、要求ごとではなく、すべての要求に対してグローバルです。コードを読み取ったときではなく、リクエストのハッシュをキーとして使用したときではありませんでした。

とにかく、それだけでした:D

これからはこういうパターンにこだわってみます...

String respBody = null;
if(response.body() != null) {
    respBody = response.body().string();
    response.body().close();
}

...応答コードの各ケースを処理する前。そうすれば、応答本文への接近呼び出しが欠落することはありません。

10
Adrián Pérez

サーバーは次のレスポンスヘッダーでキャッシュの検証を強制しています:

Cache-Control: max-age=86400, must-revalidate 

それを削除してください。

3
Jesse Wilson

私は同じ問題を抱えています、そしてokhttpソースコードをデバッグします、あなたはCacheStrategy.cacheResponseAge()を見ることができます、okhttpはnowMillisservedDateを使用します、servedDateはサーバーのhttpヘッダー「Date」から取得され、nowMillisはAndroidデバイスから取得されます。したがって、サーバー時間がデバイス時間よりも遅い場合、okhttpは取得しませんmax-ageが小さい場合はキャッシュから。

私の貧しい英語を許してください^ _ ^

2
Jack Ruan