Android Retrofit 给你的接口加上缓存

作者: 容华谢后 | 来源:发表于2023-06-13 08:22 被阅读0次
    封面

    转载请注明出处:https://www.jianshu.com/p/22ca99f690be

    本文出自 容华谢后的博客

    往期回顾:

    Android Retrofit + RxJava使用详解

    Android 探讨一下Retrofit封装的最佳姿势

    Android 谈谈我所理解的MVP

    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) {
            }
        })
    }
    

    主要看下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()
        }
    }
    

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

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

    文件目录

    3.注意

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

    // 添加响应数据缓存拦截器
    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("")
    }
    

    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)
    }
    

    5.写在最后

    GitHub地址:https://github.com/alidili/Demos/tree/master/RetrofitCacheDemo

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

    相关文章

      网友评论

        本文标题:Android Retrofit 给你的接口加上缓存

        本文链接:https://www.haomeiwen.com/subject/xgkqydtx.html