Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way of setting up cache for OkHttp in Android

Tags:

android

okhttp

I'm trying to set up the cache for OkHttp, so it only requests to the server the first time I try to retrieve a response from the server until the expires header date, or cache-control header that comes from the server invalidates the response from the cache.

Currently, its caching the response, but not using it when requesting the resource again. May be this is not the way it was supposed to be used.

I'm setting up OkHttpClient with cache like this:

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

This is used like this:

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

This is how I make one of the requests to the server with OkHttp that's actually failing to use the cache:

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

}

If I get to the directory I specified to be the cache for OkHttp I can see the journal file and 4 other files that contains the responses for some of the requests. This request (the /api one I've just pasted the code) is stored on the cache directory so it was really cached, but the filename has a .tmp at the end, like if it wasn't properly saved to the final file, like other request I made.

This is how it looks like the headers of the server response for the request:

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

And this is how OkHttp stores it on the cache:

{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

After OkHttp creates this file, it keeps requesting to the server the same resource. I can see those messages in Wireshark.

What am I doing wrong?

UPDATE:

This is now the server response after Jesse suggestion:

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

UPDATE 2: Tried the code version and found out that it is quite probable there is a bug somewhere regarding the cache. This is what I've got from the Maven output:

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

A more complete log can be seen here: https://gist.github.com/16BITBoy/344ea4c22b543f397f53

like image 627
Adrián Pérez Avatar asked Aug 08 '14 19:08

Adrián Pérez


People also ask

What is OkHttp cache in Android?

Basically: The client will send out something like timestamp or Etag of the last request. The server can then check if there is some data has changed in during that period of time or not. If nothing has changed, the server can just give a special code (304 -not modified) without sending the whole same response again.

What is OkHttp cache?

OkHttp is an efficient HTTP & HTTP/2 client for Android and Java applications. It comes with advanced features, such as connection pooling (if HTTP/2 isn't available), transparent GZIP compression, and response caching, to avoid the network completely for repeated requests.

Does Android use OkHttp?

OkHttp is an HTTP client from Square for Java and Android applications. It's designed to load resources faster and save bandwidth. OkHttp is widely used in open-source projects and is the backbone of libraries like Retrofit, Picasso, and many others.


2 Answers

I just solved the problem. It was somewhat misleading that the cache tests where failing when I tried to use OkHttp from source.

The problem was quite easy and it was that other of the request methods was getting a body on the response, and it wasn't closed at the end. That explains why I saw the ".tmp" file in the cache, but still a confusing and misleading because of the fact that this request method was consuming and closing the body from the response. Its like the lock or monitor for the cache editor is global for all requests, instead of being by request. I though it wasn't when I read the code, when it used a hash for the request as a key.

Anyway, that was it :D

From now on, I'll try to stick to a pattern like this...

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

...before handling each case for response code. That way I won't be missing a close call to the response body.

like image 55
Adrián Pérez Avatar answered Oct 09 '22 09:10

Adrián Pérez


Your server is forcing cache validation with this response header:

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

Remove that and you should be good to go.

like image 26
Jesse Wilson Avatar answered Oct 09 '22 10:10

Jesse Wilson