Android Retrofit 给你的接口加上缓存

转载请注明出处:juejin.cn/post/724417…

本文出自 容华谢后的博客

0.写在前面

最近要对接口做一些优化,于是就想着给一些频繁获取数据的接口加上缓存功能,网上搜上一搜,一般都只支持GET请求,但是因为服务器那边接口比较特殊,参数较多的获取数据接口都是用的POST,用原生的缓存方式还不行。

那只能自己实现一个,支持GET、POST请求方式,为了安全还要支持缓存数据加密,放到项目里试了试,还算比较稳定,于是便有了此篇文章。

1.流程

先看下整体的流程,还是通过OkHttp的拦截器实现的,拦截到客户端的请求,如果没有缓存,就去服务器请求数据,然后缓存到本地,然后加密。

如果有缓存,就判断下缓存的时间,没过期就返回给客户端缓存数据,过期了就再去服务器取一份,重复上面的步骤。

缓存流程

2.实现

实现一个简单的接口请求,访问百度页面,然后测试下缓存的效果:

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) {
}
})
}
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) {
        }
    })
}
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()
// 添加响应数据缓存拦截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
return httpClientBuilder.build()
}
/**
* 缓存数据拦截器
*
* @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数据写入缓存
cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
} else {
cacheFile.writeText("")
}
return response.newBuilder()
.body(data.toResponseBody(responseBody.contentType()))
.build()
}
}
/**
 * 获取OkHttpClient
 *
 * @return OkHttpClient
 */
private fun getOkHttpClient(): OkHttpClient {
    // 定制OkHttp
    val httpClientBuilder = OkHttpClient.Builder()
    // 添加响应数据缓存拦截器
    httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
    return httpClientBuilder.build()
}

/**
 * 缓存数据拦截器
 *
 * @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数据写入缓存
            cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
        } else {
            cacheFile.writeText("")
        }
        return response.newBuilder()
            .body(data.toResponseBody(responseBody.contentType()))
            .build()
    }
}
/** * 获取OkHttpClient * * @return OkHttpClient */ private fun getOkHttpClient(): OkHttpClient { // 定制OkHttp val httpClientBuilder = OkHttpClient.Builder() // 添加响应数据缓存拦截器 httpClientBuilder.addInterceptor(CacheInterceptor(this, "key")) return httpClientBuilder.build() } /** * 缓存数据拦截器 * * @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数据写入缓存 cacheFile.writeText(SecurityUtils.encryptContent(dataString, key)) } else { cacheFile.writeText("") } return response.newBuilder() .body(data.toResponseBody(responseBody.contentType())) .build() } }

代码不是很多,加密的逻辑放在SecurityUtils工具类中了,文章末尾下载源码就可以。

加密后的文件是这样的,文件里存储的内容是十六进制字符串:

文件目录

3.注意

这个key是秘钥,可以自己自定义,获取这个秘钥的方法,可以写在so里,也可以写成字符数组的方式,通过某种组合获取到,不要固定一个字符串就行,这样比较安全:

// 添加响应数据缓存拦截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
// 添加响应数据缓存拦截器
httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))
// 添加响应数据缓存拦截器 httpClientBuilder.addInterceptor(CacheInterceptor(this, "key"))

在写入缓存这里,判断了code是200就写入缓存,实际业务中,可能有自己的定义方式,code返回200只能证明接口通了,服务器的逻辑有自己的规则,比如在返回数据中也有一个code标记,可以在if判断里再加一个业务的判断,只有业务返回了成功,再写入缓存。

// 写入缓存
if (response.code == 200) {
// Json数据写入缓存
cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
} else {
cacheFile.writeText("")
}
// 写入缓存
if (response.code == 200) {
    // Json数据写入缓存
    cacheFile.writeText(SecurityUtils.encryptContent(dataString, key))
} else {
    cacheFile.writeText("")
}
// 写入缓存 if (response.code == 200) { // Json数据写入缓存 cacheFile.writeText(SecurityUtils.encryptContent(dataString, key)) } else { cacheFile.writeText("") }

4.原生GET请求缓存

有的同学只想用原生的方法去缓存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)
}
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)
}
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地址:github.com/alidili/Dem…

到这里,Retrofit的缓存功能就介绍完了,如有问题可以给我留言评论或者在GitHub中提交Issues,谢谢!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYQTdmC9' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片