Android Retrofit 給你的接口加上緩存

封面

轉(zhuǎn)載請注明出處:http://m.itdecent.cn/p/22ca99f690be

本文出自 容華謝后的博客

往期回顧:

Android Retrofit + RxJava使用詳解

Android 探討一下Retrofit封裝的最佳姿勢

Android 談?wù)勎宜斫獾腗VP

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,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容