Apollo Kotlin中的归一化缓存
Apollo Kotlin提供了两个内置的归一化缓存用于存储和重用GraphQL操作的操作结果:
您可以在应用中使用这些缓存中的一个(或两个!)来提高其大多数操作的响应性。
要开始使用更粗粒度且设置更快的缓存策略,请参阅HTTP缓存。
归一化缓存是什么?
在一个GraphQL客户端中,归一化缓存将您的每个GraphQL操作的响应分解成包含的各个单独对象。然后,每个对象根据其缓存ID作为独立条目进行缓存。这意味着如果多个响应中包含相同的对象,则该对象可以合并为一个缓存条目。这减少了缓存的整体大小,并有助于保持您缓存的日期数据一致且新鲜。
您还可以使用归一化缓存作为您UI的单一流真相,使其能够响应缓存中的更改。要了解更多关于归一化过程的信息,请参阅此博客文章。
归一化响应
看看这个例子查询:
query GetFavoriteBook {favoriteBook { # Book objectidtitleauthor { # Author objectidname}}}
此查询返回一个Book
对象,该对象又包含一个Author
对象。从GraphQL服务器的示例响应如下:
{"favoriteBook": {"id": "bk123","title": "Les Guerriers du silence","author": {"id": "au456","name": "Pierre Bordage"}}}
规范化缓存不会直接存储此响应。相反,它默认将其拆分为以下条目:
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}"}
⚠️ 这些默认生成的缓存 ID(如 favoriteBook
和 favoriteBook.author
)对于数据去重来说并不理想。请参阅 指定缓存 ID。
- 请注意,
author
字段的Book
条目现在包含字符串ApolloCacheReference{favoriteBook.author}
。这是对Author
缓存条目的引用。 - 请注意,如果已缓存至少一个查询的结果,则会始终存在
QUERY_ROOT
条目。此条目包含对查询中包含的每个顶层 field 的引用(例如,favoriteBook
)。
提供的缓存
内存缓存
Apollo Kotlin 的 MemoryCache
是一个用于存储GraphQL操作对象的规范化内存缓存。要使用它,首先将 apollo-normalized-cache
软件包添加到您的 build.gradle[.kts]
文件中的依赖项中:
dependencies {implementation("com.apollographql.apollo3:apollo-normalized-cache:3.8.5")}
然后,像这样在您的 ApolloClient
初始化中包含缓存:
// Creates a 10MB MemoryCacheFactoryval cacheFactory = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024)// Build the ApolloClientval apolloClient = ApolloClient.Builder().serverUrl("https://...")// normalizedCache() is an extension function on ApolloClient.Builder.normalizedCache(cacheFactory).build()
由于标准化缓存是可选的,normalizedCache()
是定义在 apollo-normalized-cache
模块中的 ApolloClient.Builder()
的一个扩展函数。它接受一个 NormalizedCacheFactory
参数,以便在需要时可以在主线程之外创建缓存。
一个 MemoryCache
是一个最近最少使用(LRU)缓存。它根据以下条件在内存中保持条目:
名称 | 描述 |
---|---|
maxSizeBytes | 缓存的最高大小,以字节为单位。 |
expireAfterMillis | 存在缓存条目的超时时间,以毫秒为单位。默认情况下,无超时。 |
当您的应用停止时,MemoryCache
中的数据将永远丢失。如果您需要持久化数据,可以使用 SQLite 缓存。
SQLite 缓存
Apollo Kotlin 的 SQLite 缓存使用 SQLDelight 来持久化存储数据。您可以使用它来跨应用重启持久化数据,或者当缓存数据过大无法放入内存时。
要启用 SQLite 缓存支持,将 apollo-normalized-cache-sqlite
依赖项添加到您的项目 build.gradle
文件中:
dependencies {implementation("com.apollographql.apollo3:apollo-normalized-cache-sqlite:3.8.5")}
然后根据您的平台目标(不同平台使用不同的驱动程序)将 SQLite 缓存包含在您的 ApolloClient
初始化中:
// Androidval sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("apollo.db")// JVMval sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("jdbc:sqlite:apollo.db")// iOSval sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("apollo.db")// Build the ApolloClientval apolloClient = ApolloClient.Builder().serverUrl("https://...").normalizedCache(sqlNormalizedCacheFactory).build()
然后您可以使用 SQLite 缓存,就像使用 MemoryCache
一样。
链式缓存
为了最大限度地发挥标准化缓存的优势,您可以将 MemoryCacheFactory 与 SqlNormalizedCacheFactory
链接起来:
val memoryFirstThenSqlCacheFactory = MemoryCacheFactory(10 * 1024 * 1024).chain(SqlNormalizedCacheFactory(context, "db_name"))
每当 Apollo Kotlin 尝试读取缓存数据时,它会按顺序检查每一个链式缓存直到遇到匹配项。然后它会立即返回那些缓存数据,而不读取任何额外的缓存。
每当 Apollo Kotlin 将数据写入缓存时,那些写入会沿着链式缓存逐级传播。
设置获取策略
在将标准化缓存添加到您的 ApolloClient
初始化后,Apollo Kotlin 自动使用 FetchPolicy.CacheFirst
作为所有查询的默认(客户端全局)获取策略。要更改默认设置,可以在客户端构建器上调用 fetchPolicy
:
val apolloClient = ApolloClient.Builder().serverUrl("https://...").fetchPolicy(FetchPolicy.NetworkOnly).build()
您还可以为特定的查询自定义缓存的使用方式,通过设置该查询的获取策略。
以下代码片段展示了如何设置所有可用的获取策略及其行为
val response = apolloClient.query(query)// (Default) Check the cache, then only use the network if data isn't present.fetchPolicy(FetchPolicy.CacheFirst)// Check the cache and never use the network, even if data isn't present.fetchPolicy(FetchPolicy.CacheOnly)// Always use the network, then check the cache if network fails.fetchPolicy(FetchPolicy.NetworkFirst)// Always use the network and never check the cache, even if network fails.fetchPolicy(FetchPolicy.NetworkOnly)// Execute the query.execute()
CacheAndNetwork
策略可以产生多个值,所以您应该使用 toFlow()
而不是 execute()
:
apolloClient.query(query)// Check the cache and also use the network (1 or 2 values can be emitted).fetchPolicy(FetchPolicy.CacheAndNetwork)// Execute the query and collect the responses.toFlow().collect { response ->// ...}
指定缓存 ID
query GetFavoriteBook {favoriteBook { # Book objectidtitleauthor { # Author objectidname}}}
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}"}
query AuthorById($id: String!) {author(id: $id) {idname}}}
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}"author(\"id\": \"au456\")": {"id": "au456", "name": "Pierre Bordage"}"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}", "author(\"id\": \"au456\")": "ApolloCacheReference{author(\"id\": \"au456\")}"}
- 占用的空间更多。
"Book:bk123": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{Author:au456}"}"Author:au456": {"id": "au456", "name": "Pierre Bordage"}"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference(Book:bk123)", "author(\"id\": \"au456\")": "ApolloCacheReference{Author:au456}"}
方法
有两种方法可用于指定对象的缓存 ID