【问题标题】:Caching error with Retrofit 2 and okhttp 3Retrofit 2 和 okhttp 3 的缓存错误
【发布时间】:2017-05-25 05:35:23
【问题描述】:

我正在尝试缓存来自我公司 API 的 HTTP 响应,但该应用似乎无法访问缓存目录:

W/System.err:删除失败:ENOENT(没有这样的文件或目录): /data/user/0/com.appname/cache/cache_file/journal.tmp

W/System.err: java.net.UnknownHostException:无法解析主机 “www.domain.com”:没有与主机名关联的地址

我关注了这个tutorial。以下是我设置 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);
    }
}

我尝试了一些通过互联网和 stackoverflow 找到的解决方案(仍然在上面代码中的注释中),因为起初我认为这是一个“缓存控制”重写问题。

我还在清单中添加了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 权限,但它没有任何改变。

我错过了什么吗? (我正在使用 Android 6.0.1 API 23 在三星上进行测试)

【问题讨论】:

    标签: android caching retrofit2 okhttp3 offline-caching


    【解决方案1】:

    关于第一个错误,我们看看为 OkHttpClient 提供的缓存目录:

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

    您为许多 OkHttpClient 使用了相同的缓存目录,许多实例可能会相互踩踏,破坏响应缓存。 要解决此问题,您可以只使用一次 OkHttpClient,将其配置为缓存,并在任何地方使用相同的实例。您可以尝试如下:

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

    有关 OkHttpClient 缓存的更多信息,您可以查看this link

    【讨论】:

    • 其实你可以有多个OkHttpClient的实例。重要的是在您的客户端中使用Cache 的单个实例。只是一般性评论:正如您所说,仅当您使用相同的缓存目录时才需要这样做。如果您有多个 Cache 实例,但每个实例都指向不同的目录,那也没关系。
    【解决方案2】:

    您可能缺少清单中的 INTERNET 权限。

    &lt;uses-permission android:name="android.permission.INTERNET" /&gt;

    这是出现UnknownHostException 错误的常见原因。

    或者,通过在浏览器中访问该网站,确保您可以在该设备上真正访问该网站。

    【讨论】:

    • 不,我已经添加了这个权限。事实上,我只有在关闭互联网连接(飞行模式)时才收到上述错误:我的测试包括第一次(互联网开启)获取数据以获取缓存,然后从缓存中获取数据(互联网关闭)。
    猜你喜欢
    • 2023-03-16
    • 2016-09-23
    • 2018-07-17
    • 2023-03-20
    • 1970-01-01
    • 2015-07-05
    • 2021-04-30
    • 2016-09-03
    • 1970-01-01
    相关资源
    最近更新 更多