您好,登錄后才能下訂單哦!
前言:前段時(shí)間在開(kāi)發(fā)APP的時(shí)候,經(jīng)常出現(xiàn)由于用戶設(shè)備環(huán)境的原因,拿不到從網(wǎng)絡(luò)端獲取的數(shù)據(jù),所以在APP端展現(xiàn)的結(jié)果總是一個(gè)空白的框,這種情況對(duì)于用戶體驗(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 諸如此類的問(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類:
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; /** * 字符串的緩存類 * 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)緩存,豈不笑話 { 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è)類里包含了所有的情況,一般情況下只需要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í)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。