Caching error with Retrofit 2 and okhttp 3
Asked Answered
E

2

3

I'm trying to cache HTTP responses from my company API but it looks like the app cannot access the cache directory:

W/System.err: remove failed: ENOENT (No such file or directory) : /data/user/0/com.appname/cache/cache_file/journal.tmp

W/System.err: java.net.UnknownHostException: Unable to resolve host "www.domain.com": No address associated with hostname

I have followed this tutorial. Here is how I setup Retrofit (2.1.0):

import lu.CompanyName.R;
import lu.CompanyName.interfaces.CompanyNameAPI;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import static okhttp3.logging.HttpLoggingInterceptor.Level.HEADERS;


public class Injector {

    private static final String CACHE_CONTROL = "Cache-Control";

    private static Retrofit provideRetrofit (String baseUrl) {
        return new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(provideOkHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    private static OkHttpClient provideOkHttpClient () {
        return new OkHttpClient.Builder()
                .addInterceptor(provideHttpLoggingInterceptor())
                .addInterceptor(provideOfflineCacheInterceptor())
                .addNetworkInterceptor(provideCacheInterceptor())
                .cache(provideCache())
                .build();
    }

    private static Cache provideCache () {
        /*
        Cache cache = null;
        try
        {
            File dir = CompanyName.getInstance().getExternalCacheDir();

            if (dir == null)
                dir = CompanyName.getInstance().getCacheDir();

            if (dir == null)
                Log.e("provideCache", "dir is null");

            cache = new Cache(new File(dir, "http-cache"), 10 * 1024 * 1024); // 10 MB

            if (cache == null)
                Log.e("provideCache", "cache is null");

        }
        catch (Exception e)
        {
            Log.e("provideCache", "Could not create Cache!");
        }
        return cache;*/

        /*
        File httpCacheDirectory = new File(CompanyName.getInstance().getCacheDir(), "responses");
        httpCacheDirectory.getParentFile().mkdirs();
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(httpCacheDirectory, cacheSize);
        try {
            cache.initialize();
            Iterator<String> iterator = cache.urls();
            Log.i("provideCache", "URLs in cacheHttpClient : ");
            while (iterator.hasNext()) {
                Log.i("provideCache", iterator.next());
            }
        } catch (IOException e) {
            e.printStackTrace();
            Log.i("provideCache", "CACHE NOT INIT");
        }
        return cache;*/

        return new Cache(new File(CompanyName.getInstance().getCacheDir(), "cache_file"), 20 * 1024 * 1024);
    }

    private static HttpLoggingInterceptor provideHttpLoggingInterceptor () {
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        httpLoggingInterceptor.setLevel(HEADERS);
        return httpLoggingInterceptor;
    }

    private static Interceptor provideCacheInterceptor () {
        /*return new Interceptor() {
            @Override
            public Response intercept (Chain chain) throws IOException {
                Response response = chain.proceed(chain.request());

                // re-write response header to force use of cache
                CacheControl cacheControl = new CacheControl.Builder()
                        .maxAge(2, TimeUnit.HOURS)
                        .build();

                return response.newBuilder()
                        .header(CACHE_CONTROL, cacheControl.toString())
                        .build();
            }
        };*/
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();
                String cacheHeaderValue = CompanyName.getInstance().checkIfHasNetwork()
                        ? "public, max-age=2419200"
                        : "public, only-if-cached, max-stale=2419200" ;
                Request request = originalRequest.newBuilder().build();
                Response response = chain.proceed(request);
                return response.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", cacheHeaderValue)
                        .build();
            }
        };
    }

    private static Interceptor provideOfflineCacheInterceptor () {
        /*return new Interceptor()
        {
            @Override
            public Response intercept (Chain chain) throws IOException
            {
                Request request = chain.request();

                if (!CompanyName.hasNetwork())
                {
                    CacheControl cacheControl = new CacheControl.Builder()
                            //.maxStale(7, TimeUnit.DAYS)
                            .build();

                    request = request.newBuilder()
                            .cacheControl(cacheControl)
                            .build();
                }

                return chain.proceed(request);
            }
        };*/
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();
                String cacheHeaderValue = CompanyName.getInstance().checkIfHasNetwork()
                        ? "public, max-age=2419200"
                        : "public, only-if-cached, max-stale=2419200" ;
                Request request = originalRequest.newBuilder().build();
                Response response = chain.proceed(request);
                return response.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", cacheHeaderValue)
                        .build();
            }
        };
    }

    public static CompanyNameAPI provideCompanyNameAPI () {
        return provideRetrofit(CompanyName.getInstance().getString(R.string.base_url)).create(CompanyNameAPI.class);
    }
}

I tried some solutions found over internet and stackoverflow (still in comment in the code above) because at first I thought it was a "Cache-Control" rewriting problem.

I have also added permission READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE to the manifest but it doesn't change anything.

Did I miss something? (I am testing on a Samsung with Android 6.0.1 API 23)

Ethanethane answered 10/1, 2017 at 16:36 Comment(0)
B
2

Regarding the first error, let's see the cache directory provided for OkHttpClient:

    private static OkHttpClient provideOkHttpClient () {
    return new OkHttpClient.Builder()
            .addInterceptor(provideHttpLoggingInterceptor())
            .addInterceptor(provideOfflineCacheInterceptor())
            .addNetworkInterceptor(provideCacheInterceptor())
            .cache(provideCache())
            .build();
}

You have used the same cache directory for many OkHttpClient, many instances may stomp on each other, corrupt the response cache. To fix this, you can use exactly once OkHttpClient, configure it with their cache, and use that same instance everywhere. You can try as below:

    private static Retrofit provideRetrofit (String baseUrl) {
    return new Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
}

 private static OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(provideHttpLoggingInterceptor())
            .addInterceptor(provideOfflineCacheInterceptor())
            .addNetworkInterceptor(provideCacheInterceptor())
            .cache(provideCache())
            .build();

For more info about cache with OkHttpClient, you can take a look at this link.

Bitt answered 13/1, 2017 at 6:30 Comment(1)
Actually, you can have multiple instances of OkHttpClient. The important thing is to use a single instance of Cache across your clients. And just a general comment: as you said, this is only necessary if you use the same cache directory. If you have multiple Cache instances but each of them points to a different directory, that's also fine.Arctic
I
0

You might be missing the INTERNET permission in the manifest.

<uses-permission android:name="android.permission.INTERNET" />

That's a common reason for getting UnknownHostException errors.

Alternatively, make sure you can actually reach the site in question on that device by visiting the site in a browser.

Impregnable answered 11/1, 2017 at 11:30 Comment(1)
No I have already added this permission. In fact, I got the error described above only when I turn off internet connection (airplane mode): My test consist in getting the data a first time (internet on) to get cache, and then to get data from the cache (internet off).Ethanethane

© 2022 - 2024 — McMap. All rights reserved.