一键导入
request-batching-and-caching
Layer caches (memory, disk, CDN), use ETag and stale-while-revalidate, and batch small requests to cut latency and radio cost.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Layer caches (memory, disk, CDN), use ETag and stale-while-revalidate, and batch small requests to cut latency and radio cost.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | request-batching-and-caching |
| description | Layer caches (memory, disk, CDN), use ETag and stale-while-revalidate, and batch small requests to cut latency and radio cost. |
A well-designed caching strategy lets an app feel instant even on 3G. Batching keeps the radio from waking up for every tiny write. This skill covers both.
┌────────────────────────────────────────────┐
│ View / UI │
└───────────────┬────────────────────────────┘
│
┌───────────────▼────────────────────────────┐
│ In-memory cache (per process, short TTL) │
│ e.g., MemoryCache, NSCache, LRU │
└───────────────┬────────────────────────────┘
│ miss
┌───────────────▼────────────────────────────┐
│ Disk cache (per install, minutes-hours TTL)│
│ OkHttp Cache, URLCache, Drift, Realm │
└───────────────┬────────────────────────────┘
│ miss / stale
┌───────────────▼────────────────────────────┐
│ CDN edge (seconds-minutes TTL) │
└───────────────┬────────────────────────────┘
│ miss
┌───────────────▼────────────────────────────┐
│ Origin │
└────────────────────────────────────────────┘
Every layer should honor Cache-Control and ETag. Build the client so it asks one layer, falls through on miss, and never bypasses layers silently.
Server sends:
Cache-Control: public, max-age=60, stale-while-revalidate=600, stale-if-error=86400
ETag: "abc123"
Last-Modified: Sat, 19 Apr 2026 11:20:01 GMT
Client behavior to enforce:
stale-while-revalidate, serve cached and fire a background revalidation.If-None-Match: "abc123"; accept 304 Not Modified.stale-if-error.val cache = Cache(directory = File(ctx.cacheDir, "http"), maxSize = 20L * 1024 * 1024)
val client = OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor { chain ->
val resp = chain.proceed(chain.request())
resp.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", resp.header("Cache-Control") ?: "public, max-age=60")
.build()
}.build()
For offline reads:
val request = Request.Builder()
.url(url)
.cacheControl(CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build())
.build()
let cache = URLCache(memoryCapacity: 16 * 1024 * 1024,
diskCapacity: 128 * 1024 * 1024,
directory: nil)
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .useProtocolCachePolicy
let session = URLSession(configuration: config)
Use .returnCacheDataElseLoad for an explicit stale-first fetch when rendering a cached screen.
final cacheOptions = CacheOptions(
store: HiveCacheStore(cachePath),
policy: CachePolicy.forceCache,
hitCacheOnErrorExcept: [401, 403],
maxStale: const Duration(days: 7),
priority: CachePriority.normal,
);
dio.interceptors.add(DioCacheInterceptor(options: cacheOptions));
Use a fetch wrapper or library (react-query, SWR) that already implements stale-while-revalidate:
const { data } = useQuery({
queryKey: ['feed', userId],
queryFn: () => api.getFeed(userId),
staleTime: 60_000, // fresh for 60 s
gcTime: 5 * 60_000,
refetchOnWindowFocus: true, // refresh when returning to foreground
});
Frequent small POSTs from background drain the radio. Collect and batch.
Kotlin pattern — buffer + periodic flush:
class EventBatcher(private val api: Api, scope: CoroutineScope) {
private val queue = MutableSharedFlow<Event>(extraBufferCapacity = 1_000)
init {
queue
.chunked(50, 10.seconds) // 50 events or 10 s, whichever first
.onEach { batch -> runCatching { api.send(batch) } }
.launchIn(scope)
}
fun track(event: Event) { queue.tryEmit(event) }
}
Swift pattern:
actor EventBatcher {
private var buffer: [Event] = []
private var flushTask: Task<Void, Never>?
func track(_ e: Event) {
buffer.append(e)
if buffer.count >= 50 { flushNow() } else { scheduleFlush() }
}
private func scheduleFlush() {
flushTask?.cancel()
flushTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(10))
await self?.flushNow()
}
}
private func flushNow() async {
let batch = buffer; buffer.removeAll()
guard !batch.isEmpty else { return }
try? await api.send(batch)
}
}
If multiple callers ask for the same resource simultaneously, de-dup into one in-flight request.
class RequestCoalescer<K, V>(private val load: suspend (K) -> V) {
private val inFlight = mutableMapOf<K, Deferred<V>>()
private val mutex = Mutex()
suspend fun get(key: K): V = mutex.withLock {
inFlight.getOrPut(key) {
CoroutineScope(coroutineContext).async {
try { load(key) } finally { mutex.withLock { inFlight.remove(key) } }
}
}
}.await()
}
react-query and SWR do this automatically by key.
queryClient.invalidateQueries({ queryKey: ['feed'] }).Cache, URLCache, dio_cache).Cache-Control, ETag, and stale-while-revalidate.Schedule, batch, and constrain background work with WorkManager, BGTaskScheduler, and push-over-poll patterns to minimize battery and data cost.
Attribute battery drain to CPU, radio, GPS, display, and background work using Android Battery Historian and Xcode Energy Log / Instruments Energy gauge.
Shrink APK/AAB and IPA size with R8/ProGuard, dead-code elimination, asset hygiene, and Android App Bundle / App Thinning best practices.
Cut local and CI build times on Android (Gradle config cache, build cache, KSP over KAPT) and iOS (Xcode build cache, ccache, modules). Use when clean builds exceed ten minutes.
Downsample, cache, and choose the right image format (AVIF, HEIC, WebP) to cut memory, network, and decode time on iOS and Android.
Find and fix common memory leaks caused by listeners, closures, singletons, and lifecycle mismatches on iOS, Android, Flutter, and React Native.