前言:前段時間在開發app的時候,經常出現由于用戶設備環境的原因,拿不到從網絡端獲取的數據,所以在app端展現的結果總是一個空白的框,這種情況對于用戶體驗來講是極其糟糕的,所以,苦思冥想決定對okhttp下手(因為我在項目中使用的網絡請求框架就是okhttp),則 寫了這么一個網絡數據緩存攔截器。
ok,那么我們決定開始寫了,我先說一下思路:
思路篇
既然要寫的是網絡數據緩存攔截器,主要是利用了okhttp強大的攔截器功能,那么我們應該對哪些數據進行緩存呢,或者在哪些情況下啟用數據進行緩存機制呢?
第一 :支持post請求,因為官方已經提供了一個緩存攔截器,但是有一個缺點,就是只能對get請求的數據進行緩存,對post則不支持。
第二 :網絡正常的時候,則是去網絡端取數據,如果網絡異常,比如timeoutexception unknowhostexception 諸如此類的問題,那么我們就需要去緩存取出數據返回。
第三 :如果從緩存中取出的數據是空的,那么我們還是需要讓這次請求走剩下的正常的流程。
第四 :調用者必須對緩存機制完全掌控,可以根據自己的業務需求選擇性的對數據決定是否進行緩存。
第五 :使用必須簡單,這是最最最最重要的一點。
好,我們上面羅列了五點是我們的大概思路,現在來說一下代碼部分:
代碼篇
緩存框架 :我這里使用的緩存框架是disklrucache https://github.com/jakewharton/disklrucache 這個緩存框架可以存儲到本地,也經過谷歌認可,這也是選擇這個框架的主要原因。我這里也對緩存框架進行封裝了一個cachemanager類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
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攔截器機制,智能判斷緩存場景,以及網絡情況,對不同的場景進行處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
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使用:
1
|
compile 'com.xiaolei:okhttpcacheinterceptor:1.0.0' |
由于是剛剛提交到jcenter,可能會出現拉不下來的情況(暫時還未過審核),著急的讀者可以再在你的project:build.gradle里的repositories里新增我maven的鏈接:
1
2
3
4
5
|
allprojects { repositories { maven{url 'https://dl.bintray.com/kavipyouxiang/maven' } } } |
我們新建一個項目,項目截圖是這樣的:
項目截圖
demo很簡單,一個主頁面,一個bean,一個retrofit,一個網絡請求接口
注意,因為是網絡,緩存,有關,所以,毫無疑問我們要在manifest里面添加網絡請求權限,文件讀寫權限:
1
2
3
|
<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:
1
2
3
4
5
|
client = new okhttpclient.builder() .addinterceptor( new cacheinterceptor(context)) //添加緩存攔截器,添加緩存的支持 .retryonconnectionfailure( true ) //失敗重連 .connecttimeout( 30 , timeunit.seconds) //網絡請求超時時間單位為秒 .build(); |
如果你想哪個接口的數據緩存,那么久為你的網絡接口,添加一個請求頭cacheheaders.java這個類里包含了所有的情況,一般情況下只需要cacheheaders.normal就可以了
1
2
3
4
5
6
7
|
public interface net { @headers (cacheheaders.normal) // 這里是關鍵 @formurlencoded @post ( "geocoding" ) public call<databean> getindex( @field ( "a" ) string a); } |
業務代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.jianshu.com/p/0c57d4856be1