轉(zhuǎn)載請注明出處:http://www.reibang.com/p/22ca99f690be
本文出自 容華謝后的博客
往期回顧:
0.寫在前面
最近要對接口做一些優(yōu)化,于是就想著給一些頻繁獲取數(shù)據(jù)的接口加上緩存功能孟抗,網(wǎng)上搜上一搜欺税,一般都只支持GET請求憨奸,但是因?yàn)榉?wù)器那邊接口比較特殊,參數(shù)較多的獲取數(shù)據(jù)接口都是用的POST崭孤,用原生的緩存方式還不行曲楚。
那只能自己實(shí)現(xiàn)一個(gè),支持GET唬格、POST請求方式,為了安全還要支持緩存數(shù)據(jù)加密,放到項(xiàng)目里試了試购岗,還算比較穩(wěn)定汰聋,于是便有了此篇文章。
1.流程
先看下整體的流程喊积,還是通過OkHttp的攔截器實(shí)現(xiàn)的马僻,攔截到客戶端的請求,如果沒有緩存注服,就去服務(wù)器請求數(shù)據(jù),然后緩存到本地措近,然后加密溶弟。
如果有緩存,就判斷下緩存的時(shí)間瞭郑,沒過期就返回給客戶端緩存數(shù)據(jù)辜御,過期了就再去服務(wù)器取一份,重復(fù)上面的步驟屈张。
2.實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)簡單的接口請求擒权,訪問百度頁面,然后測試下緩存的效果:
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)
// 緩存時(shí)間1小時(shí)
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)} " +
"過期時(shí)間:${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工具類中了碳抄,文章末尾下載源碼就可以。
加密后的文件是這樣的场绿,文件里存儲(chǔ)的內(nèi)容是十六進(jìn)制字符串:
3.注意
這個(gè)key是秘鑰剖效,可以自己自定義,獲取這個(gè)秘鑰的方法焰盗,可以寫在so里璧尸,也可以寫成字符數(shù)組的方式,通過某種組合獲取到熬拒,不要固定一個(gè)字符串就行爷光,這樣比較安全:
// 添加響應(yīng)數(shù)據(jù)緩存攔截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
在寫入緩存這里,判斷了code是200就寫入緩存澎粟,實(shí)際業(yè)務(wù)中蛀序,可能有自己的定義方式,code返回200只能證明接口通了活烙,服務(wù)器的邏輯有自己的規(guī)則哼拔,比如在返回?cái)?shù)據(jù)中也有一個(gè)code標(biāo)記,可以在if判斷里再加一個(gè)業(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,謝謝健民!