OKHttp3(支持Retrofit)的網(wǎng)絡(luò)數(shù)據(jù)緩存Interceptor攔截器的實(shí)現(xiàn)
前言:前段時(shí)間在開(kāi)發(fā)APP的時(shí)候,經(jīng)常出現(xiàn)由于用戶(hù)設(shè)備環(huán)境的原因,拿不到從網(wǎng)絡(luò)端獲取的數(shù)據(jù),所以在APP端展現(xiàn)的結(jié)果總是一個(gè)空白的框,這種情況對(duì)于用戶(hù)體驗(yàn)來(lái)講是極其糟糕的,所以,苦思冥想決定對(duì)OKHTTP下手(因?yàn)槲以陧?xiàng)目中使用的網(wǎng)絡(luò)請(qǐng)求框架就是OKHTTP),則 寫(xiě)了這么一個(gè)網(wǎng)絡(luò)數(shù)據(jù)緩存攔截器。
OK,那么我們決定開(kāi)始寫(xiě)了,我先說(shuō)一下思路:
思路篇
既然要寫(xiě)的是網(wǎng)絡(luò)數(shù)據(jù)緩存攔截器,主要是利用了OKHTTP強(qiáng)大的攔截器功能,那么我們應(yīng)該對(duì)哪些數(shù)據(jù)進(jìn)行緩存呢,或者在哪些情況下啟用數(shù)據(jù)進(jìn)行緩存機(jī)制呢?
第一 :支持POST請(qǐng)求,因?yàn)楣俜揭呀?jīng)提供了一個(gè)緩存攔截器,但是有一個(gè)缺點(diǎn),就是只能對(duì)GET請(qǐng)求的數(shù)據(jù)進(jìn)行緩存,對(duì)POST則不支持。
第二 :網(wǎng)絡(luò)正常的時(shí)候,則是去網(wǎng)絡(luò)端取數(shù)據(jù),如果網(wǎng)絡(luò)異常,比如TimeOutException UnKnowHostException 諸如此類(lèi)的問(wèn)題,那么我們就需要去緩存取出數(shù)據(jù)返回。
第三 :如果從緩存中取出的數(shù)據(jù)是空的,那么我們還是需要讓這次請(qǐng)求走剩下的正常的流程。
第四 :調(diào)用者必須對(duì)緩存機(jī)制完全掌控,可以根據(jù)自己的業(yè)務(wù)需求選擇性的對(duì)數(shù)據(jù)決定是否進(jìn)行緩存。
第五 :使用必須簡(jiǎn)單,這是最最最最重要的一點(diǎn)。
好,我們上面羅列了五點(diǎn)是我們的大概思路,現(xiàn)在來(lái)說(shuō)一下代碼部分:
代碼篇
緩存框架 :我這里使用的緩存框架是DiskLruCache https://github.com/JakeWharton/DiskLruCache 這個(gè)緩存框架可以存儲(chǔ)到本地,也經(jīng)過(guò)谷歌認(rèn)可,這也是選擇這個(gè)框架的主要原因。我這里也對(duì)緩存框架進(jìn)行封裝了一個(gè)CacheManager類(lèi):
import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.xiaolei.OkhttpCacheInterceptor.Log.Log; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * Created by xiaolei on 2017/5/17. */ public class CacheManager { public static final String TAG = "CacheManager"; //max cache size 10mb private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10; private static final int DISK_CACHE_INDEX = 0; private static final String CACHE_DIR = "responses"; private DiskLruCache mDiskLruCache; private volatile static CacheManager mCacheManager; public static CacheManager getInstance(Context context) { if (mCacheManager == null) { synchronized (CacheManager.class) { if (mCacheManager == null) { mCacheManager = new CacheManager(context); } } } return mCacheManager; } private CacheManager(Context context) { File diskCacheDir = getDiskCacheDir(context, CACHE_DIR); if (!diskCacheDir.exists()) { boolean b = diskCacheDir.mkdirs(); Log.d(TAG, "!diskCacheDir.exists() --- diskCacheDir.mkdirs()=" + b); } if (diskCacheDir.getUsableSpace() > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, getAppVersion(context), 1/*一個(gè)key對(duì)應(yīng)多少個(gè)文件*/, DISK_CACHE_SIZE); Log.d(TAG, "mDiskLruCache created"); } catch (IOException e) { e.printStackTrace(); } } } /** * 同步設(shè)置緩存 */ public void putCache(String key, String value) { if (mDiskLruCache == null) return; OutputStream os = null; try { DiskLruCache.Editor editor = mDiskLruCache.edit(encryptMD5(key)); os = editor.newOutputStream(DISK_CACHE_INDEX); os.write(value.getBytes()); os.flush(); editor.commit(); mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 異步設(shè)置緩存 */ public void setCache(final String key, final String value) { new Thread() { @Override public void run() { putCache(key, value); } }.start(); } /** * 同步獲取緩存 */ public String getCache(String key) { if (mDiskLruCache == null) { return null; } FileInputStream fis = null; ByteArrayOutputStream bos = null; try { DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptMD5(key)); if (snapshot != null) { fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); bos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int len; while ((len = fis.read(buf)) != -1) { bos.write(buf, 0, len); } byte[] data = bos.toByteArray(); return new String(data); } } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } /** * 異步獲取緩存 */ public void getCache(final String key, final CacheCallback callback) { new Thread() { @Override public void run() { String cache = getCache(key); callback.onGetCache(cache); } }.start(); } /** * 移除緩存 */ public boolean removeCache(String key) { if (mDiskLruCache != null) { try { return mDiskLruCache.remove(encryptMD5(key)); } catch (IOException e) { e.printStackTrace(); } } return false; } /** * 獲取緩存目錄 */ private File getDiskCacheDir(Context context, String uniqueName) { String cachePath = context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); } /** * 對(duì)字符串進(jìn)行MD5編碼 */ public static String encryptMD5(String string) { try { byte[] hash = MessageDigest.getInstance("MD5").digest( string.getBytes("UTF-8")); StringBuilder hex = new StringBuilder(hash.length * 2); for (byte b : hash) { if ((b & 0xFF) < 0x10) { hex.append("0"); } hex.append(Integer.toHexString(b & 0xFF)); } return hex.toString(); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { e.printStackTrace(); } return string; } /** * 獲取APP版本號(hào) */ private int getAppVersion(Context context) { PackageManager pm = context.getPackageManager(); try { PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); return pi == null ? 0 : pi.versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return 0; } }
緩存CacheInterceptor攔截器:利用OkHttp的Interceptor攔截器機(jī)制,智能判斷緩存場(chǎng)景,以及網(wǎng)絡(luò)情況,對(duì)不同的場(chǎng)景進(jìn)行處理。
import android.content.Context; import com.xiaolei.OkhttpCacheInterceptor.Catch.CacheManager; import com.xiaolei.OkhttpCacheInterceptor.Log.Log; import java.io.IOException; import okhttp3.FormBody; import okhttp3.Interceptor; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; /** * 字符串的緩存類(lèi) * Created by xiaolei on 2017/12/9. */ public class CacheInterceptor implements Interceptor { private Context context; public void setContext(Context context) { this.context = context; } public CacheInterceptor(Context context) { this.context = context; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); String cacheHead = request.header("cache"); String cache_control = request.header("Cache-Control"); if ("true".equals(cacheHead) || // 意思是要緩存 (cache_control != null && !cache_control.isEmpty())) // 這里還支持WEB端協(xié)議的緩存頭 { long oldnow = System.currentTimeMillis(); String url = request.url().url().toString(); String responStr = null; String reqBodyStr = getPostParams(request); try { Response response = chain.proceed(request); if (response.isSuccessful()) // 只有在網(wǎng)絡(luò)請(qǐng)求返回成功之后,才進(jìn)行緩存處理,否則,404存進(jìn)緩存,豈不笑話(huà) { ResponseBody responseBody = response.body(); if (responseBody != null) { responStr = responseBody.string(); if (responStr == null) { responStr = ""; } CacheManager.getInstance(context).setCache(CacheManager.encryptMD5(url + reqBodyStr), responStr);//存緩存,以鏈接+參數(shù)進(jìn)行MD5編碼為KEY存 Log.i("HttpRetrofit", "--> Push Cache:" + url + " :Success"); } return getOnlineResponse(response, responStr); } else { return chain.proceed(request); } } catch (Exception e) { Response response = getCacheResponse(request, oldnow); // 發(fā)生異常了,我這里就開(kāi)始去緩存,但是有可能沒(méi)有緩存,那么久需要丟給下一輪處理了 if (response == null) { return chain.proceed(request);//丟給下一輪處理 } else { return response; } } } else { return chain.proceed(request); } } private Response getCacheResponse(Request request, long oldNow) { Log.i("HttpRetrofit", "--> Try to Get Cache --------"); String url = request.url().url().toString(); String params = getPostParams(request); String cacheStr = CacheManager.getInstance(context).getCache(CacheManager.encryptMD5(url + params));//取緩存,以鏈接+參數(shù)進(jìn)行MD5編碼為KEY取 if (cacheStr == null) { Log.i("HttpRetrofit", "<-- Get Cache Failure ---------"); return null; } Response response = new Response.Builder() .code(200) .body(ResponseBody.create(null, cacheStr)) .request(request) .message("OK") .protocol(Protocol.HTTP_1_0) .build(); long useTime = System.currentTimeMillis() - oldNow; Log.i("HttpRetrofit", "<-- Get Cache: " + response.code() + " " + response.message() + " " + url + " (" + useTime + "ms)"); Log.i("HttpRetrofit", cacheStr + ""); return response; } private Response getOnlineResponse(Response response, String body) { ResponseBody responseBody = response.body(); return new Response.Builder() .code(response.code()) .body(ResponseBody.create(responseBody == null ? null : responseBody.contentType(), body)) .request(response.request()) .message(response.message()) .protocol(response.protocol()) .build(); } /** * 獲取在Post方式下。向服務(wù)器發(fā)送的參數(shù) * * @param request * @return */ private String getPostParams(Request request) { String reqBodyStr = ""; String method = request.method(); if ("POST".equals(method)) // 如果是Post,則盡可能解析每個(gè)參數(shù) { StringBuilder sb = new StringBuilder(); if (request.body() instanceof FormBody) { FormBody body = (FormBody) request.body(); if (body != null) { for (int i = 0; i < body.size(); i++) { sb.append(body.encodedName(i)).append("=").append(body.encodedValue(i)).append(","); } sb.delete(sb.length() - 1, sb.length()); } reqBodyStr = sb.toString(); sb.delete(0, sb.length()); } } return reqBodyStr; } }
以上是主體思路,以及主要實(shí)現(xiàn)代碼,現(xiàn)在來(lái)說(shuō)一下使用方式
使用方式:
gradle使用:
compile 'com.xiaolei:OkhttpCacheInterceptor:1.0.0'
由于是剛剛提交到Jcenter,可能會(huì)出現(xiàn)拉不下來(lái)的情況(暫時(shí)還未過(guò)審核),著急的讀者可以再在你的Project:build.gradle里的repositories里新增我maven的鏈接:
allprojects { repositories { maven{url 'https://dl.bintray.com/kavipyouxiang/maven'} } }
我們新建一個(gè)項(xiàng)目,項(xiàng)目截圖是這樣的:
項(xiàng)目截圖
demo很簡(jiǎn)單,一個(gè)主頁(yè)面,一個(gè)Bean,一個(gè)Retrofit,一個(gè)網(wǎng)絡(luò)請(qǐng)求接口
注意,因?yàn)槭蔷W(wǎng)絡(luò),緩存,有關(guān),所以,毫無(wú)疑問(wèn)我們要在manifest里面添加網(wǎng)絡(luò)請(qǐng)求權(quán)限,文件讀寫(xiě)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
使用的時(shí)候,你只需要為你的OKHttpClient添加一個(gè)Interceptor:
client = new OkHttpClient.Builder() .addInterceptor(new CacheInterceptor(context))//添加緩存攔截器,添加緩存的支持 .retryOnConnectionFailure(true)//失敗重連 .connectTimeout(30, TimeUnit.SECONDS)//網(wǎng)絡(luò)請(qǐng)求超時(shí)時(shí)間單位為秒 .build();
如果你想哪個(gè)接口的數(shù)據(jù)緩存,那么久為你的網(wǎng)絡(luò)接口,添加一個(gè)請(qǐng)求頭CacheHeaders.java這個(gè)類(lèi)里包含了所有的情況,一般情況下只需要CacheHeaders.NORMAL就可以了
public interface Net { @Headers(CacheHeaders.NORMAL) // 這里是關(guān)鍵 @FormUrlEncoded @POST("geocoding") public Call<DataBean> getIndex(@Field("a") String a); }
業(yè)務(wù)代碼:
Net net = retrofitBase.getRetrofit().create(Net.class); Call<DataBean> call = net.getIndex("蘇州市"); call.enqueue(new Callback<DataBean>() { @Override public void onResponse(Call<DataBean> call, Response<DataBean> response) { DataBean data = response.body(); Date date = new Date(); textview.setText(date.getMinutes() + " " + date.getSeconds() + ":\n" + data + ""); } @Override public void onFailure(Call<DataBean> call, Throwable t) { textview.setText("請(qǐng)求失??!"); } });
我們這里對(duì)網(wǎng)絡(luò)請(qǐng)求,成功了,則在界面上輸出文字,加上當(dāng)前時(shí)間,網(wǎng)絡(luò)失敗,則輸出一個(gè)請(qǐng)求失敗。
大概代碼就是這樣子的,詳細(xì)代碼,文章末尾將貼出demo地址
看效果:演示圖
這里演示了,從網(wǎng)絡(luò)正常,到網(wǎng)絡(luò)不正常,再恢復(fù)到正常的情況。
結(jié)尾
以上篇章就是整個(gè)從思路,到代碼,再到效果圖的流程,這里貼一下DEMO的地址,喜歡的可以點(diǎn)個(gè)Start
Demo地址:https://github.com/xiaolei123/OkhttpCacheInterceptor
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Retrofit之OKHttpCall源碼分析
- Okhttp、Retrofit進(jìn)度獲取的方法(一行代碼搞定)
- Android 封裝Okhttp+Retrofit+RxJava,外加攔截器實(shí)例
- okhttp3.4.1+retrofit2.1.0實(shí)現(xiàn)離線緩存的示例
- RxJava+Retrofit+OkHttp實(shí)現(xiàn)多文件下載之?dāng)帱c(diǎn)續(xù)傳
- RxJava+Retrofit+OkHttp實(shí)現(xiàn)文件上傳
- 深入淺出RxJava+Retrofit+OkHttp網(wǎng)絡(luò)請(qǐng)求
- 淺談RxJava+Retrofit+OkHttp 封裝使用
- Android中Retrofit+OkHttp進(jìn)行HTTP網(wǎng)絡(luò)編程的使用指南
- Retrofit和OkHttp如何實(shí)現(xiàn)Android網(wǎng)絡(luò)緩存
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01Java中關(guān)于ThreadLocal的隱式引用詳解
這篇文章主要介紹了Java中關(guān)于ThreadLocal的隱式引用,從線程的角度看,每個(gè)線程都保持一個(gè)對(duì)其線程局部變量副本的隱式引用,只要線程是活動(dòng)的,ThreadLocal實(shí)例就是可訪問(wèn)的,下面我們來(lái)具體看看2024-03-03Java使用EasyExcel進(jìn)行單元格合并的問(wèn)題詳解
項(xiàng)目中需要導(dǎo)出并合并指定的單元格,下面這篇文章主要給大家介紹了關(guān)于java評(píng)論、回復(fù)功能設(shè)計(jì)與實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06Java多態(tài)實(shí)現(xiàn)原理詳細(xì)梳理總結(jié)
這篇文章主要介紹了Java多態(tài)實(shí)現(xiàn)原理詳細(xì)梳理總結(jié),多態(tài)是繼封裝、繼承之后,面向?qū)ο蟮牡谌筇匦裕疚闹豢偨Y(jié)了多態(tài)的實(shí)現(xiàn)原理,需要的朋友可以參考一下2022-06-06分布式調(diào)度器之Spring Task 的使用詳解
SpringTask是Spring框架中用于任務(wù)調(diào)度的組件,通過(guò)簡(jiǎn)單的注解就能實(shí)現(xiàn)定時(shí)任務(wù)的創(chuàng)建和調(diào)度,可以通過(guò)配置線程池來(lái)實(shí)現(xiàn),本文給大家介紹分布式調(diào)度器之Spring Task 的使用,感興趣的朋友跟隨小編一起看看吧2024-10-10Java手把手必會(huì)的實(shí)例漢諾塔講解練習(xí)
漢諾塔,傳說(shuō)神在創(chuàng)造世界的時(shí)候做了三根金剛石柱子,并在一個(gè)教塔里留下了三根金剛石棒,第一根上面從上到下套著64個(gè)按從小到大排列的金盤(pán),神命令廟里的眾僧將它們一個(gè)個(gè)地從這根金剛石棒搬到另一根金剛石棒上,大盤(pán)不能放在小盤(pán)上。最后64個(gè)金盤(pán)仍然要按從小到大排列2021-09-09使用Spring-Retry解決Spring Boot應(yīng)用程序中的重試問(wèn)題
重試的使用場(chǎng)景比較多,比如調(diào)用遠(yuǎn)程服務(wù)時(shí),由于網(wǎng)絡(luò)或者服務(wù)端響應(yīng)慢導(dǎo)致調(diào)用超時(shí),此時(shí)可以多重試幾次。用定時(shí)任務(wù)也可以實(shí)現(xiàn)重試的效果,但比較麻煩,用Spring Retry的話(huà)一個(gè)注解搞定所有,感興趣的可以了解一下2023-04-04詳解使用Spring Security進(jìn)行自動(dòng)登錄驗(yàn)證
本篇文章主要介紹了詳解使用Spring Security進(jìn)行自動(dòng)登錄驗(yàn)證,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09