使用OkHttp 缓存 API 调用提高Android应用性能

使用OkHttp 缓存 API 调用提高Android应用性能

坦率地说,我们都遇到过这样的情况------焦急地刷新应用,看着加载图标不停地旋转,等待那个至关重要的 API 响应。这样的等待我们已经是炉火纯青了,是吧?手指有节奏地轻敲屏幕,微微转动着眼珠,嘴里不那么轻声地嘀咕着:"拜托,赶紧给我数据!"构建与外部 API 交互的应用时,缓存可谓是一项利器。API 调用往往速度缓慢、资源占用大,还受到 API 提供者的速率限制。但是,通过实施强大的缓存机制,你可以极大地减少 API 调用的次数、降低响应时间,并提供更加流畅的用户体验。解决重复 API 调用问题的方法之一就是在本地缓存/存储它们的响应。本文将介绍如何利用 OkHttp 库的 CacheControl 类来存储具有时间有效性的 API 响应。

实施缓存

定义缓存大小和实例

要能够将 API 调用的响应本地存储到缓存中,首先,我们需要定义缓存并通知客户端。在下面的代码片段中,我们使用 okhttp 库中的 Cache 类定义了缓存。我们将此缓存的最大大小设置为 5 MB。然后,在初始化 okhttpclient 参数时使用 cache() 函数。

kt 复制代码
//OkHttpClient.kt
// Defining a cache of 5 MB size
val cacheSize = (5 * 1024 * 1024).toLong()

//Initializing instance of Cache class
val myCache = Cache(context.cacheDir, cacheSize)

//defining okhttpclient instance
val okHttpClient = OkHttpClient.Builder()
            .cache(myCache)
            .build()

定义缓存规则

根据设备是否连接到互联网,让我们来定义一些基本的网络缓存规则:

  • 如果设备连接到互联网:如果最后一次 API 响应是在不到 30 分钟之前检索的,则显示缓存的响应;否则,获取新的响应并将其存储在缓存中。
  • 如果设备离线:使用最多 1 天前的 API 响应以保持应用程序功能。

你可以为你的项目定义不同且更为细致的规则来应对复杂的情况,但为了简单起见,我们将采用上述规则。

第一步是检查用户是否连接到互联网。为此,我们可以使用 ConnectivityManager 类收集数据并检查用户是否连接到互联网。让我们来定义一个名为 hasNetwork() 的函数,如下所示:

kt 复制代码
//CheckNetwork.kt
fun hasNetwork(context: Context): Boolean {
  val connectivityManager = context
            .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val nw      = connectivityManager.activeNetwork ?: return false
        val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
        return when {
            actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
            actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
            actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
            actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
            else -> false
        }
}

我们检查用户是否连接到WiFi、蜂窝移动网络或蓝牙网络,并根据情况返回true或false。

为了实现我们缓存规则的定义,我们将使用 OkHttp 库中的一个组件,叫做 Interceptor。拦截器是一个强大的机制,允许您在应用程序发送或接收 HTTP 请求和响应之前拦截、处理甚至修改它们。它们在通信流程中起到中间人的作用,让您更加灵活地控制 OkHttp 处理网络请求的方式。

在拦截器中,我们将提供指导,告诉我们的 okHttpClient 实例何时使用缓存响应。

在构建客户端实例时,让我们使用 addInterceptor() 函数来添加拦截器。我们可以在这个函数中修改我们的 API 请求。在 lambda 函数中,我们可以获取请求对象,并在最终发送到服务器之前对其进行修改。为了添加缓存功能,我们使用 cacheControl() 函数,该函数接受 CacheControl 类的参数。

我们需要实现的第一条规则是,如果设备连接到互联网,则使用 30 分钟前的响应(如果有的话),否则获取新的响应。我们使用 maxAge() 函数来实现这一点。

我们需要实现的第二条规则是,如果设备断开连接,则使用最多 1 天前的响应。为此,我们使用 maxStale() 函数。

kt 复制代码
// Defining a cache of 5 MB size
val cacheSize = (5 * 1024 * 1024).toLong()

//Initializing instance of Cache class
val myCache = Cache(context.cacheDir, cacheSize)

//defining okhttpclient instance
val okHttpClient = OkHttpClient.Builder()
            .cache(myCache)
            .addInterceptor { chain ->
                var request = chain.request()
                request = if (hasNetwork(context))
                    request
                        .newBuilder()
                        .cacheControl(
                            CacheControl.Builder()
                                .maxAge(30, TimeUnit.MINUTES)
                                .build()
                        )
                        .build()
                else
                    request
                        .newBuilder()
                        .cacheControl(
                            CacheControl.Builder()
                                .maxStale(1, TimeUnit.DAYS)
                                .build()
                        )
                        .build()
                chain.proceed(request)
            }
            .build()

maxAge() 与 maxStale() 的区别

max-agemax-stale 是 HTTP 缓存中用来控制响应新鲜度的指令。它们有着不同的含义:

  • max-age:指定客户端认为缓存响应是新鲜的最大时间,以秒为单位。如果响应的年龄超过了 max-age,客户端将认为该响应已过期,并尝试从服务器获取新的响应。

  • max-stale:告知客户端即使响应已过期,仍可接受,但仅在指定的最大过期时间内。同样以秒为单位。

以上是它们的区别。现在我们已经在 OkHttpClient 中实现了缓存系统,是时候为你的项目构建一个快速可靠的用户体验了。如果你使用 Retrofit 作为 OkHttp 的封装器,也可参考以下代码片段来实现缓存!

kt 复制代码
//RetrofitClient.kt 
class RetrofitClient(private val context: Context) {
    val cacheSize = (5 * 1024 * 1024).toLong()

    val instance: Api by lazy {
        val myCache = Cache(context.cacheDir, cacheSize)

        val okHttpClient = OkHttpClient.Builder()
            .cache(myCache)
            .addInterceptor { chain ->
                var request = chain.request()
                request = if (hasNetwork(context))
                    request
                        .newBuilder()
                        .cacheControl(
                            CacheControl.Builder()
                                .maxAge(30, TimeUnit.MINUTES)
                                .build()
                        )
                        .build()
                else
                    request
                        .newBuilder()
                        .cacheControl(
                            CacheControl.Builder()
                                .maxStale(1, TimeUnit.DAYS)
                                .build()
                        )
                        .build()
                chain.proceed(request)
            }
            .addInterceptor(HttpLoggingInterceptor().apply {
                this.level = HttpLoggingInterceptor.Level.BODY }
            )
            .build()

        val retrofit = Retrofit.Builder()
            .baseUrl("BASE_URL")
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()

        retrofit.create(Api::class.java)
    }
}

现在,你可以开始在实际设备或模拟器上运行你的项目了。要检查缓存是否有效,请使用Android Studio中的App Inspection标签页,或者使用OkHttp的日志拦截器来记录所有网络调用。

结论

对于长时间不变的API响应,可以通过上述技术进行缓存,从而节省大量资源。软件开发始终涉及根据时间和空间做出一定的权衡。在这种情况下,我们为了节省时间而牺牲了一点空间(用于存储API响应)。

相关推荐
运维Z叔22 分钟前
云安全 | AWS S3存储桶安全设计缺陷分析
android·网络·网络协议·tcp/ip·安全·云计算·aws
Reese_Cool2 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
平凡シンプル2 小时前
安卓 uniapp跨端开发
android·uni-app
elina80132 小时前
安卓实现导入Excel文件
android·excel
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
微刻时光2 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
趋势大仙3 小时前
SQLiteDatabase insert or replace数据不生效
android·数据库
DS小龙哥3 小时前
QT For Android开发-打开PPT文件
android·qt·powerpoint
丁总学Java3 小时前
如何使用 maxwell 同步到 redis?
数据库·redis·缓存
试行4 小时前
Android实现自定义下拉列表绑定数据
android·java