
轉(zhuǎn)載請注明出處:http://m.itdecent.cn/p/22ca99f690be
本文出自 容華謝后的博客
往期回顧:
0.寫在前面
最近要對接口做一些優(yōu)化,于是就想著給一些頻繁獲取數(shù)據(jù)的接口加上緩存功能,網(wǎng)上搜上一搜,一般都只支持GET請求,但是因為服務(wù)器那邊接口比較特殊,參數(shù)較多的獲取數(shù)據(jù)接口都是用的POST,用原生的緩存方式還不行。
那只能自己實現(xiàn)一個,支持GET、POST請求方式,為了安全還要支持緩存數(shù)據(jù)加密,放到項目里試了試,還算比較穩(wěn)定,于是便有了此篇文章。
1.流程
先看下整體的流程,還是通過OkHttp的攔截器實現(xiàn)的,攔截到客戶端的請求,如果沒有緩存,就去服務(wù)器請求數(shù)據(jù),然后緩存到本地,然后加密。
如果有緩存,就判斷下緩存的時間,沒過期就返回給客戶端緩存數(shù)據(jù),過期了就再去服務(wù)器取一份,重復(fù)上面的步驟。

2.實現(xiàn)
實現(xiàn)一個簡單的接口請求,訪問百度頁面,然后測試下緩存的效果:
val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(getOkHttpClient())
.build()
binding.btnRequest.setOnClickListener {
val service = retrofit.create(RetrofitService::class.java)
val call = service.request("https://www.baidu.com")
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
val result = response.body()?.string() ?: ""
binding.tvResult.text = result
Log.i("http返回:", result)
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
}
})
}
主要看下getOkHttpClient()方法:
/**
* 獲取OkHttpClient
*
* @return OkHttpClient
*/
private fun getOkHttpClient(): OkHttpClient {
// 定制OkHttp
val httpClientBuilder = OkHttpClient.Builder()
// 添加響應(yīng)數(shù)據(jù)緩存攔截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
return httpClientBuilder.build()
}
/**
* 緩存數(shù)據(jù)攔截器
*
* @param mContext Context
* @param key 秘鑰
*/
private class CacheInterceptor(
private val mContext: Context,
private val key: String
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val request = chain.request()
val cacheKey = HttpUtils.getCacheKey(request)
val cacheFile = File(HttpUtils.getCacheFile(mContext), cacheKey)
// 緩存時間1小時
val cacheTime = 3600000L
val cacheEnable = (System.currentTimeMillis() - cacheFile.lastModified()) < cacheTime
if (cacheEnable && cacheFile.exists() && cacheFile.length() > 0) {
Log.i(
"CacheInterceptor",
"[intercept] 緩存模式 url:${HttpUtils.getRequestUrl(request)} " +
"過期時間:${HttpUtils.dateTimeToString(cacheFile.lastModified() + cacheTime)}"
)
val cache = SecurityUtils.decryptContent(cacheFile.readText(), key)
if (cache.isNotEmpty() && cache.startsWith("{") && cache.endsWith("}")) {
return okhttp3.Response.Builder()
.code(200)
.body(cache.toResponseBody())
.request(request)
.message("from disk cache")
.protocol(Protocol.HTTP_2)
.build()
}
}
val response = chain.proceed(request)
val responseBody = response.body ?: return response
val data = responseBody.bytes()
val dataString = String(data)
// 寫入緩存
if (response.code == 200) {
// Json數(shù)據(jù)寫入緩存
cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
} else {
cacheFile.writeText("")
}
return response.newBuilder()
.body(data.toResponseBody(responseBody.contentType()))
.build()
}
}
代碼不是很多,加密的邏輯放在SecurityUtils工具類中了,文章末尾下載源碼就可以。
加密后的文件是這樣的,文件里存儲的內(nèi)容是十六進制字符串:

3.注意
這個key是秘鑰,可以自己自定義,獲取這個秘鑰的方法,可以寫在so里,也可以寫成字符數(shù)組的方式,通過某種組合獲取到,不要固定一個字符串就行,這樣比較安全:
// 添加響應(yīng)數(shù)據(jù)緩存攔截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
在寫入緩存這里,判斷了code是200就寫入緩存,實際業(yè)務(wù)中,可能有自己的定義方式,code返回200只能證明接口通了,服務(wù)器的邏輯有自己的規(guī)則,比如在返回數(shù)據(jù)中也有一個code標(biāo)記,可以在if判斷里再加一個業(yè)務(wù)的判斷,只有業(yè)務(wù)返回了成功,再寫入緩存。
// 寫入緩存
if (response.code == 200) {
// Json數(shù)據(jù)寫入緩存
cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
} else {
cacheFile.writeText("")
}
4.原生GET請求緩存
有的同學(xué)只想用原生的方法去緩存GET請求,在此附上代碼:
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
// GET請求
if ("GET" == request.method) {
return if (checkNetwork(mContext)) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
val response = chain.proceed(request)
response.newBuilder()
.header("Cache-Control", "public, max-age=0")
.removeHeader("Pragma")
.build()
} else {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
val response = chain.proceed(request)
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=604800")
.removeHeader("Pragma")
.build()
}
}
// POST請求
return chain.proceed(request)
}
5.寫在最后
GitHub地址:https://github.com/alidili/Demos/tree/master/RetrofitCacheDemo
到這里,Retrofit的緩存功能就介紹完了,如有問題可以給我留言評論或者在GitHub中提交Issues,謝謝!