您好,登錄后才能下訂單哦!
前言:前段時間在開發APP的時候,經常出現由于用戶設備環境的原因,拿不到從網絡端獲取的數據,所以在APP端展現的結果總是一個空白的框,這種情況對于用戶體驗來講是極其糟糕的,所以,苦思冥想決定對OKHTTP下手(因為我在項目中使用的網絡請求框架就是OKHTTP),則 寫了這么一個網絡數據緩存攔截器。
OK,那么我們決定開始寫了,我先說一下思路:
思路篇
既然要寫的是網絡數據緩存攔截器,主要是利用了OKHTTP強大的攔截器功能,那么我們應該對哪些數據進行緩存呢,或者在哪些情況下啟用數據進行緩存機制呢?
第一 :支持POST請求,因為官方已經提供了一個緩存攔截器,但是有一個缺點,就是只能對GET請求的數據進行緩存,對POST則不支持。
第二 :網絡正常的時候,則是去網絡端取數據,如果網絡異常,比如TimeOutException UnKnowHostException 諸如此類的問題,那么我們就需要去緩存取出數據返回。
第三 :如果從緩存中取出的數據是空的,那么我們還是需要讓這次請求走剩下的正常的流程。
第四 :調用者必須對緩存機制完全掌控,可以根據自己的業務需求選擇性的對數據決定是否進行緩存。
第五 :使用必須簡單,這是最最最最重要的一點。
好,我們上面羅列了五點是我們的大概思路,現在來說一下代碼部分:
代碼篇
緩存框架 :我這里使用的緩存框架是DiskLruCache https://github.com/JakeWharton/DiskLruCache 這個緩存框架可以存儲到本地,也經過谷歌認可,這也是選擇這個框架的主要原因。我這里也對緩存框架進行封裝了一個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(); } } } /** * 同步設置緩存 */ 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(); } } } } /** * 異步設置緩存 */ 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攔截器機制,智能判斷緩存場景,以及網絡情況,對不同的場景進行處理。
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端協議的緩存頭 { 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()) // 只有在網絡請求返回成功之后,才進行緩存處理,否則,404存進緩存,豈不笑話 { ResponseBody responseBody = response.body(); if (responseBody != null) { responStr = responseBody.string(); if (responStr == null) { responStr = ""; } CacheManager.getInstance(context).setCache(CacheManager.encryptMD5(url + reqBodyStr), responStr);//存緩存,以鏈接+參數進行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); // 發生異常了,我這里就開始去緩存,但是有可能沒有緩存,那么久需要丟給下一輪處理了 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));//取緩存,以鏈接+參數進行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方式下。向服務器發送的參數 * * @param request * @return */ private String getPostParams(Request request) { String reqBodyStr = ""; String method = request.method(); if ("POST".equals(method)) // 如果是Post,則盡可能解析每個參數 { 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; } }
以上是主體思路,以及主要實現代碼,現在來說一下使用方式
使用方式:
gradle使用:
compile 'com.xiaolei:OkhttpCacheInterceptor:1.0.0'
由于是剛剛提交到Jcenter,可能會出現拉不下來的情況(暫時還未過審核),著急的讀者可以再在你的Project:build.gradle里的repositories里新增我maven的鏈接:
allprojects { repositories { maven{url 'https://dl.bintray.com/kavipyouxiang/maven'} } }
我們新建一個項目,項目截圖是這樣的:
項目截圖
demo很簡單,一個主頁面,一個Bean,一個Retrofit,一個網絡請求接口
注意,因為是網絡,緩存,有關,所以,毫無疑問我們要在manifest里面添加網絡請求權限,文件讀寫權限:
<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)//網絡請求超時時間單位為秒 .build();
如果你想哪個接口的數據緩存,那么久為你的網絡接口,添加一個請求頭CacheHeaders.java這個類里包含了所有的情況,一般情況下只需要CacheHeaders.NORMAL就可以了
public interface Net { @Headers(CacheHeaders.NORMAL) // 這里是關鍵 @FormUrlEncoded @POST("geocoding") public Call<DataBean> getIndex(@Field("a") String a); }
業務代碼:
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("請求失敗!"); } });
我們這里對網絡請求,成功了,則在界面上輸出文字,加上當前時間,網絡失敗,則輸出一個請求失敗。
大概代碼就是這樣子的,詳細代碼,文章末尾將貼出demo地址
看效果:演示圖
這里演示了,從網絡正常,到網絡不正常,再恢復到正常的情況。
結尾
以上篇章就是整個從思路,到代碼,再到效果圖的流程,這里貼一下DEMO的地址,喜歡的可以點個Start
Demo地址:https://github.com/xiaolei123/OkhttpCacheInterceptor
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。