1. 程式人生 > >【快取策略】Retrofit+OkHttp實現快取處理

【快取策略】Retrofit+OkHttp實現快取處理

早先對於伺服器資料快取處理一般是本地SP或者Sqlite;現在網路請求改為Retrofit+OkHttp,OkHttp是有快取策略的,

今天我們就來說怎麼實現Retrofit與OkHttp的快取實現。

使用快取的目的

減少伺服器負荷,降低延遲提升使用者體驗。複雜的快取策略會根據使用者當前的網路情況採取不同的快取策略,比如在2g網路很差的情況下,提高快取使用的時間;不用的應用、業務需求、介面所需要的快取策略也會不一樣,需要資料的實時性,採用快取就是無意義!根據實際應用情況,制定自己的快取策略。

Retrofit+OkHttp的快取機制

在響應請求之後在 data/data/<包名>/cache 下建立一個response 資料夾,保持快取資料。
這樣我們就可以在請求的時候,如果判斷到沒有網路,自動讀取快取的資料。
同樣這也可以實現,在我們沒有網路的情況下,重新開啟App可以瀏覽的之前顯示過的內容。
也就是:判斷網路,有網路,則從網路獲取,並儲存到快取中,無網路,則從快取中獲取。

實現快取

1、開啟快取 這一步是設定快取路徑,以及快取大小
<span style="font-size:14px;">File httpCacheDirectory = new File(context.getExternalCacheDir(), "responses");
//設定快取 10M
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
client = new OkHttpClient.Builder().cache(cache).build();</span>
2、設定 OkHttp 攔截器
主要是攔截操作,包括控制快取的最大生命值,控制快取的過期時間

兩個操作都是在 Interceptor 中進行的

  • 通過 CacheControl 控制快取資料
 CacheControl.Builder cacheBuilder = new CacheControl.Builder();
 cacheBuilder.maxAge(0, TimeUnit.SECONDS);//多次訪問一個介面,<span style="white-space:pre">	</span>設定請求快取時間,超過時間重新請求,否則去快取
 cacheBuilder.maxStale(365,TimeUnit.DAYS);//這個是控制快取的過時時間
 CacheControl cacheControl = cacheBuilder.build();
  • 設定攔截器
Request request = chain.request();
if(!StateUtils.isNetworkAvailable(MyApp.mContext)){
 request = request.newBuilder()
         .cacheControl(cacheControl)
         .build();
}
Response originalResponse = chain.proceed(request);
if (StateUtils.isNetworkAvailable(MyApp.mContext)) {
 int maxAge = 60; // read from cache
 return originalResponse.newBuilder()
         .removeHeader("Pragma")
         .header("Cache-Control", "public ,max-age=" + maxAge)
         .build();
} else {
 int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
 return originalResponse.newBuilder()
         .removeHeader("Pragma")
         .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
         .build();
}

.maxAge(0,TimeUnit.SECONDS)設定的時間比攔截器長是不起效果

設定比攔截器設定的時間短就會以這個時間為主,我覺得是為了方便控制。

maxStale(365, TimeUnit.DAYS)設定的是過時時間,okthhp快取分成了兩個來考慮,一個是為了請求時直接拿快取省流量,一個是為了下次進入應用時可以直接拿快取。

直接上程式碼

private HttpControl(final Context context) {
        cookieStore = new PersistentCookieStore(context);
        CookieHandler cookieHandler = new CookieManager(cookieStore,
                CookiePolicy.ACCEPT_ALL);

        Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();

                /**
                 * 未聯網獲取快取資料
                 */
                if (!CheckHasNet.isNetWorkOk(context)) {
                    //在20秒快取有效,此處測試用,實際根據需求設定具體快取有效時間
                    CacheControl cacheControl = new CacheControl.Builder()
                            .maxStale(30, TimeUnit.DAYS)
                            .build();
                    request = request.newBuilder()
                            .cacheControl(cacheControl)
                            .build();
                }

                return chain.proceed(request);
            }
        };

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Log.d("MyTAG", "OkHttp: " + message);
            }
        });
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        File httpCacheDirectory = new File(context.getExternalCacheDir(), "responses");
        //設定快取 10M
        Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
        //1.建立Retrofit物件
        client = new OkHttpClient.Builder().addInterceptor(logging)
                .addInterceptor(interceptor)//離線
                .addNetworkInterceptor(provideCacheInterceptor())//線上
                .cache(cache)
                .readTimeout(30000, TimeUnit.MILLISECONDS)
                .connectTimeout(30000, TimeUnit.MILLISECONDS)
                .cookieJar(new JavaNetCookieJar(cookieHandler))
                .build();
        retrofit = new Retrofit.Builder().client(client).baseUrl(Constant.BASEURL)// 定義訪問的主機地址
                .validateEagerly(true)
//                .addConverterFactory(GsonConverterFactory.create())//解析方法 Gson
                .addConverterFactory(JsonConverterFactory.create())//解析方法
//                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();


    }

    public 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
                // 正常訪問同一請求介面(多次訪問同一介面),給30秒快取,超過時間重新發送請求,否則取快取資料
                CacheControl cacheControl = new CacheControl.Builder()
                        .maxAge(3, TimeUnit.SECONDS )
                        .build();

                return response.newBuilder()
                        .header("Cache-Control", cacheControl.toString() )
                        .build();
            }
        };
    }

    public PersistentCookieStore getCookieStore() {
        return cookieStore;
    }

    /**
     * 單例模式
     *
     * @return
     */
    public static HttpControl getInstance(Context context) {
        if (instance == null) {
            synchronized (HttpControl.class) {
                if (instance == null) {
                    instance = new HttpControl(context);
                }
            }
        }
        return instance;
    }

    public OkHttpClient getClient() {
        return client;
    }

    public void setClient(OkHttpClient client) {
        this.client = client;
    }

    public <T> T create(final Class<T> service) {
        return retrofit.create(service);
    }


注意

1、快取是在每一次網路請求之後,重新儲存的,所以在超過快取過期時間後,Retrofit會在檢查到沒快取之後自動請求網路伺服器資料

2、快取資料也是需要網路下載的,所以在網路不好的情況下,可能不能立即快取

3、根據自身實際情況,制定快取策略