OKHttp3(支持Retrofit)的網(wǎng)絡(luò)數(shù)據(jù)緩存Interceptor攔截器的實現(xiàn)
前言:前段時間在開發(fā)APP的時候,經(jīng)常出現(xiàn)由于用戶設(shè)備環(huán)境的原因,拿不到從網(wǎng)絡(luò)端獲取的數(shù)據(jù),所以在APP端展現(xiàn)的結(jié)果總是一個空白的框,這種情況對于用戶體驗來講是極其糟糕的,所以,苦思冥想決定對OKHTTP下手(因為我在項目中使用的網(wǎng)絡(luò)請求框架就是OKHTTP),則 寫了這么一個網(wǎng)絡(luò)數(shù)據(jù)緩存攔截器。
OK,那么我們決定開始寫了,我先說一下思路:
思路篇
既然要寫的是網(wǎng)絡(luò)數(shù)據(jù)緩存攔截器,主要是利用了OKHTTP強大的攔截器功能,那么我們應該對哪些數(shù)據(jù)進行緩存呢,或者在哪些情況下啟用數(shù)據(jù)進行緩存機制呢?
第一 :支持POST請求,因為官方已經(jīng)提供了一個緩存攔截器,但是有一個缺點,就是只能對GET請求的數(shù)據(jù)進行緩存,對POST則不支持。
第二 :網(wǎng)絡(luò)正常的時候,則是去網(wǎng)絡(luò)端取數(shù)據(jù),如果網(wǎng)絡(luò)異常,比如TimeOutException UnKnowHostException 諸如此類的問題,那么我們就需要去緩存取出數(shù)據(jù)返回。
第三 :如果從緩存中取出的數(shù)據(jù)是空的,那么我們還是需要讓這次請求走剩下的正常的流程。
第四 :調(diào)用者必須對緩存機制完全掌控,可以根據(jù)自己的業(yè)務(wù)需求選擇性的對數(shù)據(jù)決定是否進行緩存。
第五 :使用必須簡單,這是最最最最重要的一點。
好,我們上面羅列了五點是我們的大概思路,現(xiàn)在來說一下代碼部分:
代碼篇
緩存框架 :我這里使用的緩存框架是DiskLruCache https://github.com/JakeWharton/DiskLruCache 這個緩存框架可以存儲到本地,也經(jīng)過谷歌認可,這也是選擇這個框架的主要原因。我這里也對緩存框架進行封裝了一個CacheManager類:
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/*一個key對應多少個文件*/, 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); } /** * 對字符串進行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版本號 */ 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攔截器機制,智能判斷緩存場景,以及網(wǎng)絡(luò)情況,對不同的場景進行處理。
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; /** * 字符串的緩存類 * 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ò)請求返回成功之后,才進行緩存處理,否則,404存進緩存,豈不笑話 { ResponseBody responseBody = response.body(); if (responseBody != null) { responStr = responseBody.string(); if (responStr == null) { responStr = ""; } CacheManager.getInstance(context).setCache(CacheManager.encryptMD5(url + reqBodyStr), responStr);//存緩存,以鏈接+參數(shù)進行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ā)生異常了,我這里就開始去緩存,但是有可能沒有緩存,那么久需要丟給下一輪處理了 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ù)進行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,則盡可能解析每個參數(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; } }
以上是主體思路,以及主要實現(xiàn)代碼,現(xiàn)在來說一下使用方式
使用方式:
gradle使用:
compile 'com.xiaolei:OkhttpCacheInterceptor:1.0.0'
由于是剛剛提交到Jcenter,可能會出現(xiàn)拉不下來的情況(暫時還未過審核),著急的讀者可以再在你的Project:build.gradle里的repositories里新增我maven的鏈接:
allprojects { repositories { maven{url 'https://dl.bintray.com/kavipyouxiang/maven'} } }
我們新建一個項目,項目截圖是這樣的:
項目截圖
demo很簡單,一個主頁面,一個Bean,一個Retrofit,一個網(wǎng)絡(luò)請求接口
注意,因為是網(wǎng)絡(luò),緩存,有關(guān),所以,毫無疑問我們要在manifest里面添加網(wǎng)絡(luò)請求權(quán)限,文件讀寫權(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" />
使用的時候,你只需要為你的OKHttpClient添加一個Interceptor:
client = new OkHttpClient.Builder() .addInterceptor(new CacheInterceptor(context))//添加緩存攔截器,添加緩存的支持 .retryOnConnectionFailure(true)//失敗重連 .connectTimeout(30, TimeUnit.SECONDS)//網(wǎng)絡(luò)請求超時時間單位為秒 .build();
如果你想哪個接口的數(shù)據(jù)緩存,那么久為你的網(wǎng)絡(luò)接口,添加一個請求頭CacheHeaders.java這個類里包含了所有的情況,一般情況下只需要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("請求失?。?); } });
我們這里對網(wǎng)絡(luò)請求,成功了,則在界面上輸出文字,加上當前時間,網(wǎng)絡(luò)失敗,則輸出一個請求失敗。
大概代碼就是這樣子的,詳細代碼,文章末尾將貼出demo地址
看效果:演示圖
這里演示了,從網(wǎng)絡(luò)正常,到網(wǎng)絡(luò)不正常,再恢復到正常的情況。
結(jié)尾
以上篇章就是整個從思路,到代碼,再到效果圖的流程,這里貼一下DEMO的地址,喜歡的可以點個Start
Demo地址:https://github.com/xiaolei123/OkhttpCacheInterceptor
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Retrofit之OKHttpCall源碼分析
- Okhttp、Retrofit進度獲取的方法(一行代碼搞定)
- Android 封裝Okhttp+Retrofit+RxJava,外加攔截器實例
- okhttp3.4.1+retrofit2.1.0實現(xiàn)離線緩存的示例
- RxJava+Retrofit+OkHttp實現(xiàn)多文件下載之斷點續(xù)傳
- RxJava+Retrofit+OkHttp實現(xiàn)文件上傳
- 深入淺出RxJava+Retrofit+OkHttp網(wǎng)絡(luò)請求
- 淺談RxJava+Retrofit+OkHttp 封裝使用
- Android中Retrofit+OkHttp進行HTTP網(wǎng)絡(luò)編程的使用指南
- Retrofit和OkHttp如何實現(xiàn)Android網(wǎng)絡(luò)緩存
相關(guān)文章
Java中關(guān)于ThreadLocal的隱式引用詳解
這篇文章主要介紹了Java中關(guān)于ThreadLocal的隱式引用,從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的,ThreadLocal實例就是可訪問的,下面我們來具體看看2024-03-03Java多態(tài)實現(xiàn)原理詳細梳理總結(jié)
這篇文章主要介紹了Java多態(tài)實現(xiàn)原理詳細梳理總結(jié),多態(tài)是繼封裝、繼承之后,面向?qū)ο蟮牡谌筇匦?,本文只總結(jié)了多態(tài)的實現(xiàn)原理,需要的朋友可以參考一下2022-06-06分布式調(diào)度器之Spring Task 的使用詳解
SpringTask是Spring框架中用于任務(wù)調(diào)度的組件,通過簡單的注解就能實現(xiàn)定時任務(wù)的創(chuàng)建和調(diào)度,可以通過配置線程池來實現(xiàn),本文給大家介紹分布式調(diào)度器之Spring Task 的使用,感興趣的朋友跟隨小編一起看看吧2024-10-10使用Spring-Retry解決Spring Boot應用程序中的重試問題
重試的使用場景比較多,比如調(diào)用遠程服務(wù)時,由于網(wǎng)絡(luò)或者服務(wù)端響應慢導致調(diào)用超時,此時可以多重試幾次。用定時任務(wù)也可以實現(xiàn)重試的效果,但比較麻煩,用Spring Retry的話一個注解搞定所有,感興趣的可以了解一下2023-04-04