使用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响应)。

相关推荐
muyouking116 小时前
Tauri Android 开发踩坑实录:从 Gradle 版本冲突到离线构建成功
android·rust
Achou.Wang7 小时前
源码分析 golang bigcache 高性能无 GC 开销的缓存设计实现
开发语言·缓存·golang
Jerry7 小时前
Compose 为元素赋予动画特效
android
Jeled8 小时前
协程工具类
android·android studio
阿兰哥11 小时前
【调试篇5】TransactionTooLargeException 原理解析
android·性能优化·源码
爱吃水蜜桃的奥特曼12 小时前
玩Android Flutter版本,通过项目了解Flutter项目快速搭建开发
android·flutter
太过平凡的小蚂蚁12 小时前
Android 版本特性完全解析:从6.0到16.0的实用指南
android
杨筱毅12 小时前
【底层机制】【Android】深入理解UI体系与绘制机制
android·底层机制
介一安全13 小时前
【Frida Android】基础篇8:Java层Hook基础——调用带对象参数的方法
android·网络安全·逆向·安全性测试·frida
puyaCheer13 小时前
Android 13 启动的时候会显示一下logo,很不友好
android·gitee