多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > 手動緩存Retrofit+OkHttp響應體,不再局限于Get請求緩存

手動緩存Retrofit+OkHttp響應體,不再局限于Get請求緩存

來源:程序員人生   發布時間:2016-06-30 12:45:42 閱讀次數:4975次

轉載請標明出處:
http://blog.csdn.net/iamzgx/article/details/51764848
本文出自:【iGoach的博客】

概括

這篇博客是接著上1篇博客學會Retrofit+OkHttp+RxAndroid3劍客的使用,讓自己緊跟Android潮流的步伐,沒看過的,建議看完上1篇再來看這篇。在上1篇博客中僅僅是簡單的講授了OkHttp的緩存問題,主要是通過http協議里面的control-cache控制緩存,而且是僅僅只能是Get要求才能緩存,如果Post要求OkHttp會讓response返回null,同時報504毛病,也就是沒緩存。okhttp為何要這樣做呢?通過查看緩存的文件,我們可以發現,OkHttp緩存的是全部http要求的信息,所以這就和http協議有關系了。在RESTful API里面,我們把Get要求理解為從服務端查詢數據,Post要求理解為更新服務端數據,而http協議里面緩存通常只適用于idempotent request,也就是Get要求,為何只適應Get要求?我們都知道Get要求url結合提交參數是唯1標示,而Post要求的參數是在http的body體里面,是可變的,沒法成為唯1的標示。但是,我們在項目中基本上每個接口都要提交基本參數,1般用的都是Post要求。Get要求還不太安全,要求的路徑大小還有限制。既然OkHttp有限制。那末我們可以自己手動緩存。

android的緩存處理

既然要手動緩存,那末我們就要來看看android里面手動緩存有哪些。主要有兩種方式,1種是sqlite緩存,1種是文件緩存。

  • sqlite緩存
    目前有很多第3方sqlite框架,比如可以結合GreenDao來做緩存,1個緩存對應1個表。把url路經,下載時間,過期時間等信息都寄存到數據庫。然后把url做為要求的唯1標示,在有網的情況下,判斷當前要求url緩存是不是存在,存在就要移除數據庫里面的緩存,然后緩存新的緩存,在沒有網絡的情況下,判斷緩存是不是過期,然落后行數據庫操作。從這里我們可以看出,數據庫操作還是比較頻繁的,1不留心,就會出現利用性能問題,ANR問題,指針問題。而且android數據庫是放在/data/data/<包名>/databases/目錄下,它會占用利用內存的,1但緩存很多的話,就要及時去清算緩存,很麻煩。

  • 文件緩存
    為何說文件緩存更好呢?如果SD存在的話,我們可以把緩寄存在SD的/data/data/<包名>/cache目錄下,不存在SD的話,再放在/data/data/<包名>下面。即便內存再多,也不會影響利用的內置利用空間。文件緩存1般都會通過DiskLruCache實現,DiskLruCache是硬盤緩存,即便利用進程結束了,緩存還是存在的。當利用卸載時,改目錄的數據也會清除掉,不會留下殘余數據。DiskLruCache緩存,沒有甚么過期時間之說,只要它存在文件里面,我們就能夠隨時去讀取它。下面我們就用DiskLruCache對Retrofit+OkHttp的響應體進行緩存。這里我們只緩存json數據。

DiskLruCache的使用方法

獲得DiskLruCache對象

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

不能直接通過new的方法創建,要通過調用DiskLruCache.open()這個方法獲得,有4個參數,File指的是緩存的存儲路徑,1般優先存儲于SD卡的 /sdcard/Android/data/<包名>/cache 路徑下,如果SD卡不存在,再存在/data/data/<包名>/cache 這個路徑下,判斷代碼以下

private File getDiskCacheDir(Context context, String uniqueName) { String cachePath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //如果SD卡存在通過getExternalCacheDir()獲得路徑, cachePath = context.getExternalCacheDir().getPath(); } else { //如果SD卡不存在通過getCacheDir()獲得路徑, cachePath = context.getCacheDir().getPath(); } //放在路徑 /.../data/<application package>/cache/uniqueName return new File(cachePath + File.separator + uniqueName); }

appVersion指的是版本號,可以指利用的版本號,valueCount指的就是1個key對應多少個文件,1般我們指定1個文件,1對1使得后面更好獲得。maxSize指的是緩存的最大大小,1般傳入5M或10M就夠了。

寫入緩存

首先我們先獲得1個DiskLruCache.Editor對象,代碼以下

public DiskLruCache.Editor editor(String key) { try { key = Utils.hashKeyForDisk(key); //wirte DIRTY DiskLruCache.Editor edit = mDiskLruCache.edit(key); //edit maybe null :the entry is editing if (edit == null) { Log.w(TAG, "the entry spcified key:" + key + " is editing by other . "); } return edit; } catch (IOException e) { e.printStackTrace(); } return null; }

首先進行的是Utils.hashKeyForDisk(key),也就是通過MD5生成唯1的要求標示,這樣就能夠通過key來獲得DiskLruCache.Editor實例。獲得到實例后就能夠獲得到OutputStream,然后通過BufferedWriter寫入,以下代碼

public void put(String key, String value) { DiskLruCache.Editor edit = null; BufferedWriter bw = null; try { edit = editor(key); if (edit == null) return; OutputStream os = edit.newOutputStream(0); bw = new BufferedWriter(new OutputStreamWriter(os)); bw.write(value); edit.commit();//write CLEAN } catch (IOException e) { e.printStackTrace(); try { //s edit.abort();//write REMOVE } catch (IOException e1) { e1.printStackTrace(); } } finally { try { if (bw != null) bw.close(); } catch (IOException e) { e.printStackTrace(); } } }

讀取緩存

首先是通過key獲得DiskLruCache.Snapshot實例,然后得到InputStream,以下代碼

public InputStream get(String key) { try { DiskLruCache.Snapshot snapshot = mDiskLruCache.get(Utils.hashKeyForDisk(key)); if (snapshot == null) //not find entry , or entry.readable = false { Log.e(TAG, "not find entry , or entry.readable = false"); return null; } //write READ return snapshot.getInputStream(0); } catch (IOException e) { e.printStackTrace(); return null; } }

然后就是InputStreamReader讀取,以下代碼

public String getAsString(String key) { InputStream inputStream = null; inputStream = get(key); if (inputStream == null) return null; String str = null; try { str = Util.readFully(new InputStreamReader(inputStream, Util.UTF_8)); } catch (IOException e) { e.printStackTrace(); try { inputStream.close(); } catch (IOException e1) { e1.printStackTrace(); } } return str; } static String readFully(Reader reader) throws IOException { try { StringWriter writer = new StringWriter(); char[] buffer = new char[1024]; int count; while ((count = reader.read(buffer)) != -1) { writer.write(buffer, 0, count); } return writer.toString(); } finally { reader.close(); } }

然后就是刪除操作

public boolean remove(String key) { try { key = Utils.hashKeyForDisk(key); return mDiskLruCache.remove(key); } catch (IOException e) { e.printStackTrace(); } return false; }

直接remove掉就ok了。

DiskLruCache的封裝

從Github里面搜索DiskLruCache,可以看到鴻洋大神的base-diskcache框架,它主要是把diskcache封裝成和AsimpleCache框架1樣,挺好用的。
使用方法以下(來源于base-diskcache框架)

存 put(String key, Bitmap bitmap) put(String key, byte[] value) put(String key, String value) put(String key, JSONObject jsonObject) put(String key, JSONArray jsonArray) put(String key, Serializable value) put(String key, Drawable value) editor(String key).newOutputStream(0);//原本的方式String getAsString(String key); JSONObject getAsJson(String key) JSONArray getAsJSONArray(String key) <T> T getAsSerializable(String key) Bitmap getAsBitmap(String key) byte[] getAsBytes(String key) Drawable getAsDrawable(String key) InputStream get(String key);//原本的用法

這里我只是保存響應的json,只用到

put(String key, String value)

String getAsString(String key);

兩個方法,至于key使用要求參數生成的MD5做為唯1的標示。

下面就使用這個DiskLruCache封裝進行手動緩存,DiskLruCache的源碼和封裝代碼可以去鴻洋的github上下載。

HRetrofitNetHelper代碼的修改

基于上1篇博客的HRetrofitNetHelper對象。進行代碼修改,修改點以下

  • 去除OkHttp的cache緩存配置
  • 去除mUrlInterceptor的攔截器
  • 改在call的onresponse里面進行操作
  • enqueueCall方法配置成鏈式編程配置

然后再貼上全部的代碼,注意幾個修改點就行了。

public class HRetrofitNetHelper implements HttpLoggingInterceptor.Logger { public static HRetrofitNetHelper mInstance; public Retrofit mRetrofit; public OkHttpClient mOkHttpClient; public HttpLoggingInterceptor mHttpLogInterceptor; private BasicParamsInterceptor mBaseParamsInterceptor; private Context mContext; public Gson mGson; //DiskLruCache封裝的幫助類, private DiskLruCacheHelper diskLruCacheHelper; public static final String BASE_URL = "http://192.168.1.102:8080/GoachWeb/"; private Action1<String> onNextAction; private HRetrofitNetHelper(Context context){ this.mContext = context ; createSubscriberByAction(); mGson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss") .create(); mHttpLogInterceptor = new HttpLoggingInterceptor(this); mHttpLogInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); Map<String,String> tempParams = getBaseParams(); mBaseParamsInterceptor = new BasicParamsInterceptor.Builder() .addParamsMap(tempParams) .build(); try { //創建DiskLruCacheHelper 對象 diskLruCacheHelper = new DiskLruCacheHelper(mContext); } catch (IOException e) { e.printStackTrace(); } //這里去除緩存配置和mUrlInterceptor的配置 mOkHttpClient = new OkHttpClient.Builder() .connectTimeout(12, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .addInterceptor(mHttpLogInterceptor) .addInterceptor(mBaseParamsInterceptor) .build(); mRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(mGson)) .client(mOkHttpClient) .build(); } public static HRetrofitNetHelper getInstance(Context context){ if(mInstance==null){ synchronized (HRetrofitNetHelper.class){ if(mInstance==null){ mInstance = new HRetrofitNetHelper(context); } } } return mInstance; } public <T> T getAPIService(Class<T> service) { return mRetrofit.create(service); } /*這里改成鏈式編程,默許是不緩存。在不緩存的情況下,只需配置Call<BaseResp<D>>實例,也就是調用上面getAPIService方法獲得的實例。然后就是retrofitCallBack回調接口,如果需要緩存的情況,那末就要再配置isCache為true,然后配置Type(主要是Gson解析泛型會報錯Java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to,所以需再傳遞這個參數進行解析),最后調用start方法進行要求*/ public static final class enqueueCall{ boolean isCache; Type clazz; Call call; RetrofitCallBack retrofitCallBack; HRetrofitNetHelper mRetrofitNetHelper; private Context mContext; public Gson mGson; private DiskLruCacheHelper diskLruCacheHelper; public enqueueCall(HRetrofitNetHelper retrofitNetHelper){ isCache = false; this.mRetrofitNetHelper = retrofitNetHelper; this.mContext = retrofitNetHelper.mContext; this.mGson = retrofitNetHelper.mGson; this.diskLruCacheHelper = retrofitNetHelper.diskLruCacheHelper; } public <D> enqueueCall call(Call<BaseResp<D>> call){ this.call = call ; return this; } public enqueueCall clazz(Type clazz){ this.clazz = clazz ; return this; } public <D> enqueueCall retrofitCallBack(RetrofitCallBack<D> retrofitCallBack){ this.retrofitCallBack = retrofitCallBack ; return this; } public enqueueCall isCache(boolean isCache){ this.isCache = isCache ; return this; } public <D> enqueueCall start(){ call.enqueue(new Callback<BaseResp<D>>() { @Override public void onResponse(Call<BaseResp<D>> call, Response<BaseResp<D>> response) { //獲得要求Request Request request = call.request(); //獲得要求的url String requestUrl = call.request().url().toString(); //去獲得返回數據 BaseResp<D> resp = response.body() ; //去獲得RequestBody RequestBody requestBody = request.body(); //緩存格式為utf⑻ Charset charset = Charset.forName("UTF⑻"); //去獲得要保存的key String key=""; //如果是Post要求,要通過Buffer去讀取body體里面的參數 if(method.equals("POST")){ MediaType contentType = requestBody.contentType(); if (contentType != null) { charset = contentType.charset(Charset.forName("UTF⑻")); } Buffer buffer = new Buffer(); try { requestBody.writeTo(buffer); } catch (IOException e) { e.printStackTrace(); } key = buffer.readString(charset); buffer.close(); }else{ //如果不是Post要求,比如Get要求,那末久通過url做為唯1標識 key = requestUrl; } Log.d("zgx","response==========key"+key); //處理特殊接口,如果是登錄接口進行彈框提示 if(!TextUtils.isEmpty(requestUrl)){ if(requestUrl.contains("LoginDataServlet")) { if (Looper.myLooper() == null) { Looper.prepare(); } mRetrofitNetHelper.createObservable("現在要求的是登錄接口"); } } //分為有網和沒網的情況下 //如果有網 if(NetUtil.checkNetwork(mContext)!=NetUtil.NO_NETWORK){ //如果返回數據為null if(resp==null){ //回調失敗接口 if(retrofitCallBack!=null) retrofitCallBack.onFailure("暫無數據"); }else{ //如果是接口返回2000或2001或2002,進行彈框提示 if (resp.getResultCode() == 2000 || resp.getResultCode() == 2001 || resp.getResultCode() == 2002) { Toast.makeText(mContext,"code====="+resp.getResultCode(),Toast.LENGTH_SHORT).show(); } //如果接口返回200,并且http要求code返回200,說明要求成功 if (resp.getResultCode() == 200&&response.code()==200) { if(retrofitCallBack!=null){ //需要緩存數據 String cacheResponse = mGson.toJson(resp); //判斷下當前是不是存在key緩存的數據,如果存在移除掉, if(!TextUtils.isEmpty(key)&&!TextUtils.isEmpty(diskLruCacheHelper.getAsString(key))) diskLruCacheHelper.remove(key); //當需要緩存的數據不為空的時候,并且需要緩存的時候,通過diskLruCacheHelper進行緩存 if(!TextUtils.isEmpty(key)&&!TextUtils.isEmpty(cacheResponse)&&isCache){ Log.d("zgx","response========cacheResponse"+cacheResponse); diskLruCacheHelper.put(key,cacheResponse); } //然后就是回調成功接口 retrofitCallBack.onSuccess(resp); } } else { //這個是要求失敗,那末就回調失敗接口 // ToastMaker.makeToast(mContext, resp.errMsg, Toast.LENGTH_SHORT); if(retrofitCallBack!=null) retrofitCallBack.onFailure(resp.getErrMsg()); } } return; } //沒有網絡的情況下,去獲得key對應的緩存 String json = diskLruCacheHelper.getAsString(key); //如果緩存不存在,那末久回調失敗接口 if(json==null){ Toast.makeText(mContext, "沒有緩存!", Toast.LENGTH_SHORT).show(); if(retrofitCallBack!=null){ retrofitCallBack.onFailure("沒有緩存!"); } }else{ //判斷是不是配置clazz,1定要先配置,要不然Gson解析出錯 if(clazz==null){ throw new IllegalArgumentException("請先配置clazz"); } //解析緩存數據,然落后行回調成功接口 resp = mGson.fromJson(json,clazz); if(retrofitCallBack!=null){ retrofitCallBack.onSuccess(resp); } } } @Override public void onFailure(Call<BaseResp<D>> call, Throwable t) { // ToastMaker.makeToast(mContext, "網絡毛病,請重試!", Toast.LENGTH_SHORT); if(retrofitCallBack!=null){ retrofitCallBack.onFailure(t.toString()); } } }); return this; } } //.....省略,和上篇博客代碼1樣 //這里我們改成通過diskLruCacheHelper封裝的類進行刪除緩存 public void clearCache() throws IOException { diskLruCacheHelper.delete(); } }

主要修改的地方,上面基本上都注釋到了,這里沒有做緩存的過期時間,有網的情況下,還是保持數據的實時性,沒網的情況下才會去讀取緩存。

API修改成Post要求

ILoginService.class

public interface ILoginService { @FormUrlEncoded @POST("LoginDataServlet") Call<BaseResp<RegisterBean>> userLogin(@Field("username") String username, @Field("password") String password); }

INewsService.class

public interface INewsService { @FormUrlEncoded @POST("NewsDataServlet") Call<BaseResp<News<NewItem>>> userNews(@Field("userId") String userId); }

這里主要是測試這兩個接口

要求修改成鏈式編程

登錄要求修改代碼以下

首先實現回調接口

//傳入成功回調的BaseResp<T>的泛型T為RegisterBean implements HRetrofitNetHelper.RetrofitCallBack<RegisterBean>

然后是Call要求配置

final Call<BaseResp<RegisterBean>> repos = loginService.userLogin(username,password); new HRetrofitNetHelper .enqueueCall(HRetrofitNetHelper.getInstance(this)) .call(repos)//repos指的是retrofitNetHelper.getAPIService返回的API .retrofitCallBack(this)//配置回調接口 .isCache(true)//設置需要緩存 .clazz(new TypeToken<BaseResp<RegisterBean>>(){}.getType())//Gson解析緩存需要 .start();//真正開始發起要求

然后實現兩個回調方法

@Override public void onSuccess(BaseResp<RegisterBean> baseResp) { Date date = baseResp.getResponseTime(); if(baseResp.getData().getErrorCode()==1){ Toast.makeText(getBaseContext(),"登錄成功",Toast.LENGTH_SHORT).show(); }else { Toast.makeText(getBaseContext(),"用戶不存在",Toast.LENGTH_SHORT).show(); } mDialog.dismiss(); } @Override public void onFailure(String error) { Log.d("zgx","onFailure======"+error); mDialog.dismiss(); }

如果新聞頁也要緩存,那末代碼同理修改以下。

private void loadData(){ INewsService newService = retrofitNetHelper.getAPIService(INewsService.class); Log.d("zgx","mUserId====="+mUserId); final Call<BaseResp<News<NewItem>>> repos = newService.userNews(mUserId); new HRetrofitNetHelper.enqueueCall(HRetrofitNetHelper.getInstance(this)) .call(repos) .retrofitCallBack(this) .isCache(true) .clazz(new TypeToken<BaseResp<News<NewItem>>>(){}.getType()) .start(); }

這樣就緩存了登錄接口的數據和新聞頁面的數據。
下面就來測試下,只緩存登錄接口。測試結果為有網的情況下,根據上面代碼知道登錄成功會彈出登錄成功的Toast,并且會生成緩存文件,沒有網絡的情況下會去讀取緩存,并且還是會彈出Toast提示,登錄失敗不彈。效果以下

這里寫圖片描述

接下來我們再看下沒有緩存的效果,代碼只要修改不配置

HRetrofitNetHelper.enqueueCall(HRetrofitNetHelper.getInstance(this)) .call(repos) .retrofitCallBack(this) .start();

然后就來看效果,有網的情況下應當為登錄成功,沒網的情況下,提示沒有緩存,效果以下

這里寫圖片描述

Get要求效果同理。一樣可以得到這樣的效果,感興趣的可以去試下。

最后配置3個權限

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

整體感覺Retrofit+OkHttp框架用起來還是很方便的。特別是響應式編程,用的特別爽。還有就是Retrofit的源碼設計的特別完善。不過在這里,用RxAndroid用的還是比較少,相信以后會用的愈來愈多,而且現在谷歌的agera響應式編程也出來了。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 亚洲综合首页 | 国产h视频在线 | 中文字幕在线观看 | 小说区图片区综合视频区 | 手机看片精品国产福利盒子 | 国产精品久久久久久久久久免费 | 久久er国产精品免费观看8 | 日韩人成| 久久99精品国产99久久 | 日韩国产欧美成人一区二区影院 | 婷婷在线视频国产综合 | 最近手机版免费中文字幕 | 欧美亚洲国产精品久久久久 | 国产激情一区二区三区在线观看 | 亚洲精品国产啊女成拍色拍 | 日本精a在线观看 | 亚洲黄色在线观看视频 | www在线观看视频免费 | 最新亚洲一区二区三区四区 | 91精品国产色综合久久不 | 亚洲女人影院想要爱 | www.日韩精品 | 色博影院 | 亚洲在线成人 | 欧美人与物videos另 | 国内精品久久久久影院不卡 | 国产一区二区不卡 | 国产国语videosex | 欧美日韩在线精品一区二区三区 | 欧美97| 国产精品国产三级国产专不∫ | 高清视频一区二区三区 | 中文字幕精品在线观看 | 秋霞免费手机理论视频在线观看 | 欧美一级毛片日本 | 视频一区视频二区在线观看 | v视界成人影院在线视频 | 国产免费a级片 | 永久免费视频v片www | 国产亚洲欧美一区二区 | 亚洲视频免费观看 |