【问题标题】:Retrofit2 - how to cache only certain API callsRetrofit2 - 如何仅缓存某些 API 调用
【发布时间】:2017-11-20 15:22:41
【问题描述】:

我只想缓存某些调用,而不是全部。这是我现在拥有的代码,但它会影响所有改造调用:

int cacheSize = 10 * 1024 * 1024; // 10 MB  
Cache cache = new Cache(getCacheDir(), cacheSize);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .build();

Retrofit.Builder builder = new Retrofit.Builder()  
    .baseUrl("http://10.0.2.2:3000/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create());

Retrofit retrofit = builder.build(); 

当我只想为那个调用做缓存时,我可以在标题中放入一些东西吗?

例如:

// 我在想我可以在标题中放一些东西。我只想缓存客户端(android)端的调用。所以我在想改造可以记住响应并将其缓存以供下一次调用,但我不希望它用于我所有的调用,只是我想要的那些,可能是 1 或 2。其余的可以一直连接到服务器。这是如何实现的?

@Headers("Cache-Control:????XXXXX) //is it possible this way ?, how ?
@GET("getBusiness.action")// Store information 
Call<RestaurantInfoModel> getRestaurantInfo(@Query("userId") String userId,@Query("businessId") String businessId);

更新

这是我现在尝试过的:

这是我构建 okhttpclient 的方法:

final OkHttpClient.Builder builder = new OkHttpClient.Builder();
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(getCacheDir(), cacheSize);
if (BuildConfig.RETROFIT_LOG_ALL) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    builder.addInterceptor(logging);
}
builder.cache(cache);
return builder.build();

稍后我将它添加到改造中,它可以工作,但似乎没有缓存。让我们在不向 okhttp 添加缓存的情况下查看标头响应:

Content-Type: application/json; charset=utf-8
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Connection: close
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Transfer-Encoding: chunked
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: X-Powered-By: Express
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Vary: Origin
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Access-Control-Allow-Credentials: true
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Cache-Control: public, max-age=0
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: ETag: W/"39ba-G9evSsiVDp9GAVGu1Mk4ag"
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Date: Sun, 18 Jun 2017 10:54:58 GMT

所以这是我为查看缓存是否正常工作所做的测试。我使用这个向 api 发出请求:

public interface CountriesApi {
    @GET("countries")
    @Headers({"Content-Type:application/json"})
    Observable<List<CountryModel>> getCountries();
}

然后我在 android 设备上关闭互联网并尝试在应用程序中再次拨打相同的电话。但相反,我得到了改造,抱怨没有网络连接。相反,它应该刚刚从缓存中获取。知道有什么问题吗?

java.net.SocketException: Network is unreachable at
java.net.PlainSocketImpl.socketConnect(Native Method) at
java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:334) at
java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:196)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:178) at
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356) at
java.net.Socket.connect(Socket.java:586) at
okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:63)
at
okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:223)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:149)
at
okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:192)
at
okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
at
okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
at
okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at
okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:211)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at
com.mobile.retrofit.NetworkSessionManager.intercept(NetworkSessionManager.java:38)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185) at
okhttp3.RealCall.execute(RealCall.java:69) at
retrofit2.OkHttpCall.execute(OkHttpCall.java:180) at
retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
io.reactivex.internal.operators.observable.ObservableFlatMap.subscribeActual(ObservableFlatMap.java:55)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452) at
io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
at
io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
at java.util.concurrent.FutureTask.run(FutureTask.java:237) at
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761) 

【问题讨论】:

标签: android caching http-headers retrofit2


【解决方案1】:

我也在寻找相同的解决方案,他们想出了这个解决方案:

首先创建一个单例改造类:

public class RetrofitClient {

private static final String BASE_URL = "BASE URL HERE...";

private static RetrofitClient mInstance;

private final Retrofit retrofit;
private final Retrofit retrofitCache;

private static final String HEADER_CACHE_CONTROL = "Cache-Control";
private static final String HEADER_PRAGMA = "Pragma";
int cacheSize = 10 * 1024 * 1024; // 10 MB

public static synchronized RetrofitClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RetrofitClient(context);
    }
    return mInstance;
}

private RetrofitClient(Context context) {
    retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    retrofitCache = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient(context))
            .addConverterFactory(GsonConverterFactory.create())
            .build();

}

public ApiInterface getApi() {
    return retrofit.create(ApiInterface.class);
}

public ApiInterface getCachedApi() {
    return retrofitCache.create(ApiInterface.class);
}

Interceptor networkInterceptor() {
    return chain -> {
        CacheControl cacheControl = new CacheControl.Builder()
                .maxAge(5, TimeUnit.MINUTES) //cache validity time
                .build();

        Response response = chain.proceed(chain.request());

        return response.newBuilder()
                .removeHeader(HEADER_PRAGMA)
                .removeHeader(HEADER_CACHE_CONTROL)
                .header(HEADER_CACHE_CONTROL, cacheControl.toString())
                .build();
    };
}

OkHttpClient okHttpClient(Context context) {
    Cache cache = new Cache(context.getCacheDir(), cacheSize);

    return new OkHttpClient.Builder()
            .cache(cache)
            .addNetworkInterceptor(networkInterceptor())
            .build();
}

}

然后像这样创建API接口:

public interface ApiInterface {

    @GET("apiEndPoint")
    Call<ResponseBody> getData();
}

然后像这样在Activity 中使用RetrofitClient

//Without Cache
Call<AboutResponse> call =
            RetrofitClient.getInstance(this)
                    .getApi()
                    .getAbout();

/With Cache
Call<AboutResponse> call =
            RetrofitClient.getInstance(this)
                    .getCachedApi()
                    .getAbout();

【讨论】:

    【解决方案2】:

    使用自定义注释

    @Target(AnnotationTarget.FUNCTION)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class Cacheable
    
    class MainInterceptor : Interceptor {
    
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
            val builder = request.newBuilder()
            request.tag(Invocation::class.java)?.let {
                if (!it.method().isAnnotationPresent(Cacheable::class.java)) {
                    builder.cacheControl(CacheControl.Builder()
                        .noStore()
                        .build())
                    return chain.proceed(builder.build())
                }
                try {
                    builder.cacheControl(CacheControl.FORCE_NETWORK)
                    return chain.proceed(builder.build())
                } catch (e: Throwable) {
                    e.printStackTrace()
                }
                builder.cacheControl(CacheControl.Builder()
                    .maxStale(Int.MAX_VALUE, TimeUnit.DAYS)
                    .build())
            }
            return chain.proceed(builder.build())
        }
    }
    
    
    interface ServerApi {
    
        @Cacheable
        @GET("some_path")
        fun getSmth(): Call<ResponseBody>
    }
    

    给okhttp实例添加缓存不要丢了

    OkHttpClient.Builder()
        .cache(Cache(context.cacheDir, 10 * 1024 * 1024L))
        .addInterceptor(MainInterceptor())
    //...
    

    【讨论】:

    • 我希望避免反思……不过,这是一个有趣的想法。
    【解决方案3】:

    caching (doc) 是 OkHttp 库,它主要由响应头驱动。您应该检查它们并验证 API 响应是否被缓存。 OkHttp logging interceptor 应该有助于调试。

    要检查网络请求是否正在发生,请使用 HttpLoggingInterceptor 作为网络拦截器,而不是应用程序拦截器(请参阅https://github.com/square/okhttp/wiki/Interceptors):

    HttpLoggingInterceptor interceptor = HttpLoggingInterceptor();
    interceptor.setLevel(Level.BASIC);
    OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();
    

    当请求被缓存处理时,不会调用网络拦截器。

    下一步将视情况而定。

    1) 所有 API 请求都被缓存。

    那么你应该能够为你不想缓存的方法使用注解@Headers("Cache-Control: no-cache")。

    2) 不缓存所有 API 请求。

    然后您可以更改您的 API(添加适当的缓存标头),也可以为 OkHttp 实施网络拦截器,该拦截器将修改响应(标头)。您可以在此回复中找到一些灵感:Can Retrofit with OKHttp use cache data when offline

    【讨论】:

    • 我如何从日志中判断它是否是缓存副本?我想如果我切断互联网,我可以很容易地分辨出来,但改造会抛出一个异常,即没有网络连接,我看不到或得到任何东西api 响应。我还注意到我的最大年龄是 0,这很重要。根据我的帖子,我让 okhttpclient 使用它在构建器中获取的缓存对象配置缓存。
    • 您可以将日志拦截器添加为网络拦截器(在 Ob 的 Builder 对象上添加网络拦截器)。网络拦截器仅在访问网络时调用,因此缓存的响应不会显示在日志中。我将扩展响应以包含此内容。
    • 是的,API 发送的 Cache-Control: max-age=0 标头很重要,并且会强制 OkHttp 每次都进行网络请求。
    • 感谢您对网络拦截器的评论。我不得不做两个。一个应用程序拦截器和一个网络拦截器,并根据我需要在请求的生命周期内完成的操作分别添加它们。感谢您在缓存响应中不显示日志记录的提示。我最终添加了一个改造动态标头,这样我就可以随时控制缓存。喜欢这篇文章的解释krtkush.github.io/2016/06/02/caching-using-okhttp-part-2.html
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 2020-07-16
    • 1970-01-01
    • 1970-01-01
    • 2019-07-29
    相关资源
    最近更新 更多