Android Retrofit 给你的接口加上缓存

转载请注明出处:https://blog.csdn.net/kong_gu_you_lan/article/details/131200501?spm=1001.2014.3001.5501

本文出自 容华谢后的博客

往期回顾:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/29318.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

export 和 export default 的区别

一、铺垫 ES6模块化主要包含以下3种用法&#xff1a; ① 默认导出和默认导入---export default ② 按需导出和按需导入---export ③ 直接导入并执行模块中的代码 二、默认导出 语法&#xff1a;export default 需要共享的成员 创建一个共享的数据 order.js let name…

详解Eureka服务注册和调用

目录 1.概述 2.环境 2.1.项目结构 2.2.项目架构 2.3.依赖 3.注册中心 3.1.配置使用 3.2.管理页 3.3.集群 4.服务注册 5.服务调用 5.2.Ribbon 5.3.Feign 5.4.OpenFeign 1.概述 我们把从外部进入到微服务集群里的流量称为“南北流量”、微服务集群节点间流转的流量…

从零开发短视频电商 Jmeter插件安装和常用插件

Jmeter插件安装和常用插件 插件安装方式 一种是手动安装各种插件&#xff0c;下载对应的jar包&#xff0c;放到lib\ext目录下就可以使用了。另一种是通过漂亮的 UI &#xff0c;jmeter 插件管理器Plugins Manager可以方便的管理其他插件的下载和更新。安装一次插件管理器&…

数据库SQL查询(一)

本文介绍SQL查询&#xff0c;如何在海量数据中筛选想要数据&#xff1b; 数据库管理系统选择&#xff1a;关系型数据库mysql 数据库管理工具选择&#xff1a;navicat 本文中查询语句和查询案例参考自&#xff1a;https://edu.csdn.net/course/detail/27673?ops_request_mis…

面试专题:计算机网络常见面试点总结

socket、tcp、udp、http 的认识及区别 socket、tcp、udp、http 的认识及区别​ 一、先来一个讲TCP、UDP和HTTP关系的 1、TCP/IP是个协议组&#xff0c;可分为三个层次&#xff1a;网络层、传输层和应用层。 在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。 在传…

【Django 网页Web开发】24. 实战项目:moudleForm的文件上传应用到城市管理(17)(保姆级图文)

目录 用户上传文件存放media如何启用1. 在urls.py中进行配置&#xff1a;2. 在settings.py中进行配置&#xff1a;3. 能够通过media的url访问文件 moudleForm上传文件实现城市管理1. moudle.py2. url.py3. city.py4. city.html5. 文件上传小结6. 城市管理效果总结 欢迎关注 『D…

基于Java菜匣子优选系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

人脸识别4:Android InsightFace实现人脸识别Face Recognition(含源码)

人脸识别4&#xff1a;Android InsightFace实现人脸识别Face Recognition(含源码) 目录 人脸识别4&#xff1a;Android InsightFace实现人脸识别Face Recognition(含源码) 1. 前言 2. 项目说明 &#xff08;1&#xff09;开发版本 &#xff08;2&#xff09;依赖库说明(O…

Docker部署gitlab-runner

gitlab-runner 1.部署 Linux使用二进制的方式Docker中使用容器的方式启动gitlab-runnerHelm包的方式安装gitlab-runner Docker中使用容器的方式启动gitlab-runner 1.安装gitlab runner docker run -d --name gitlab-runner --restart always \ -v /srv/gitlab-runner/conf…

When viruses are good for you 病毒,有时对人体是有益的 | 经济学人20230506版社论双语精翻

本篇来自《经济学人》&#xff08;The Economist&#xff09;2023年5月6日社论&#xff08;Leaders&#xff09;精选&#xff1a;《病毒&#xff0c;有时对人体是有益的》&#xff08;When viruses are good for you&#xff09;。 Bacteriophages 噬菌体 When viruses are goo…

202319读书笔记|《春之海终日优哉游哉:谢芜村俳句300》——远山峡谷间樱花绽放,宇宙在其中

202319读书笔记&#xff5c;《春之海终日优哉游哉&#xff1a;谢芜村俳句300》——远山峡谷间樱花绽放&#xff0c;宇宙在其中 与谢芜村&#xff0c;小林一茶&#xff0c;芭蕉&#xff0c;与谢野晶子&#xff0c;俳句都很赞&#xff0c;虽多本书中略有重复&#xff0c;但多多观…

Android插件化框架-Shadow原理解析

作者&#xff1a;dennyz 1、前言 所谓插件化&#xff0c;是实现动态化的一种具体的技术手段。 对于移动端的App而言&#xff0c;无论是Android还是iOS&#xff0c;都存在一个共同的问题&#xff0c;那就是更新的周期较长。 当我们希望快速为App更新功能时&#xff0c;必须经…

【redis】redis的5种数据结构及其底层实现原理

文章目录 redis中的数据结构redis数据结构底层实现stringlisthashsetintset字典 zset跳表插入删除过程 redis中的数据结构 Redis支持五种数据类型&#xff1a;string&#xff08;字符串&#xff09;&#xff0c;hash&#xff08;哈希&#xff09;&#xff0c;list&#xff08;…

Python如何制作图标点选验证码

本文讲解如何使用python中的opencv库来制作图标点选验证码 图标点选验证码制作起来非常简单,你只需要准备两部分数据集,数据集数量都不用很多,背景图我选择了20个左右,大小为(300, 500)左右,图标我抓取了100多个,图标大小为(40,40)左右,图标由不同大小的透明度构成…

C++:IO流

目录 一. C语言的输入输出方式 二. C的输入输出 2.1 C标准IO流 2.2 文件IO流 2.3 字符串IO流 一. C语言的输入输出方式 一般C语言通过scanf和printf来实现输入输出&#xff0c;scanf和printf都需要显示地指定数据类型。同时&#xff0c;C语言还支持fscanf/fprintf以及ssc…

【大数据】可视化仪表板 - Superset的安装和使用

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

MYSQL中 find_in_set() 函数用法详解

MYSQL中 find_in_set() 函数用法详解 官方涵义&#xff08;MySQL手册中语法说明&#xff09; FIND_IN_SET(str,strlist) &#xff1a; str 要查询的字符串&#xff0c;strlist 需查询的字段&#xff0c;参数以”,”分隔&#xff0c;形式如 (1,2,6,8,10,22)&#xff1b;该函数的…

接口如何运用pytest+HttpRunner展开测试?

目录 前言&#xff1a; 一、 什么是接口测试 二、 引入自动化背景 三、 自动化技术选型 四、 自动化测试用例 五、自动化成果 前言&#xff1a; pytest和HttpRunner都是Python编程语言中常用的接口测试框架。 pytest是一种成熟的、灵活的、社区支持良好的测试框架&…

vr沉浸式仿真实训展厅中控系统提高课堂纪律

为解决实训教学过程中“看不到、进不去、成本高、危险大”的问题&#xff0c;VR智能中控系统为职业教育及高等教育老师提供一个数字化、沉浸式、集中管控的实训教学工具。 VR智能中控系统通过对VR教学课堂的实时监控、数据的收集和分析&#xff0c;为气象学院的教学提供更多帮助…

2023年05月份青少年软件编程Scratch试卷三级真题

2023-05 Scratch三级真题 分数&#xff1a;100 题数&#xff1a;38 测试时长&#xff1a;60min 一、单选题(共25题&#xff0c;共50分) 1. 关于变量&#xff0c;下列描述错误的是&#xff1f;&#xff08;A&#xff09;&#xff08;2分&#xff09; &#xff08;变量那一栏…