Skip to content

Instantly share code, notes, and snippets.

@seventhmoon
Created September 11, 2026 14:19
Show Gist options
  • Select an option

  • Save seventhmoon/ca29e2866d9fdb4ff8e6897a58d19d8b to your computer and use it in GitHub Desktop.

Select an option

Save seventhmoon/ca29e2866d9fdb4ff8e6897a58d19d8b to your computer and use it in GitHub Desktop.
Cache Manager for Image Loaders
package com.example.cache
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.app.ApplicationExitInfo
import android.content.ComponentCallbacks2
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.os.Build
import android.util.LruCache
import android.view.WindowManager
import androidx.annotation.MainThread
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.window.layout.WindowMetricsCalculator
import kotlin.math.max
import kotlin.math.min
class UnifiedAdaptiveCacheManager private constructor(
private val app: Application
) : ComponentCallbacks2, DefaultLifecycleObserver {
private val activityManager = app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
private val windowManager = app.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// Hardware Baselines
val totalRamMb: Int
val hardwareCapMb: Int
val bandwidthMultiplier: Float
val exitMultiplier: Float
val preferredConfig: Bitmap.Config
// Dynamic State
@Volatile var appStateMultiplier: Float = 1.0f
@Volatile var activeGridColumns: Int = 2
// The Underlying Bitmap Cache
private var memoryCache: LruCache<String, Bitmap>
init {
// 1. Detect Total RAM & Infer AOSP MemoryLimiter Tier
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
totalRamMb = (memInfo.totalMem / (1024 * 1024)).toInt()
// 2. Set AOSP MemoryLimiter Caps (from /system/etc/memory-limiter-config.xml)
hardwareCapMb = when {
totalRamMb < 4800 -> 24 // 4GB tier [3200, 4800)
totalRamMb < 6800 -> 36 // 6GB tier [4800, 6800)
totalRamMb < 9216 -> 48 // 8GB tier [6800, 9216)
totalRamMb < 14336 -> 72 // 12GB tier [9216, 14336)
else -> 96 // 16GB tier [14336, 18432)
}
// 3. Bandwidth Proxy & Pixel Format Modulation
if (totalRamMb < 4800) {
bandwidthMultiplier = 0.70f
preferredConfig = Bitmap.Config.RGB_565 // 2 Bytes Per Pixel
} else if (totalRamMb < 9216) {
bandwidthMultiplier = 1.00f
preferredConfig = Bitmap.Config.ARGB_8888 // 4 Bytes Per Pixel
} else {
bandwidthMultiplier = 1.20f
preferredConfig = Bitmap.Config.ARGB_8888
}
// 4. Closed-Loop Exit Telemetry Feedback
exitMultiplier = checkHistoricalExitFeedback()
// 5. Initialize Cache with Default Bounds
val initialSizeBytes = computeOptimalCacheSizeBytes(app)
memoryCache = object : LruCache<String, Bitmap>(initialSizeBytes) {
override fun sizeOf(key: String, bitmap: Bitmap): Int = bitmap.byteCount
}
// Register Observers
app.registerComponentCallbacks(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
/**
* Master Formula Implementation
*/
fun computeOptimalCacheSizeBytes(context: Context): Int {
// A. Dynamic Window Surface Bounds (Foldable & Split-screen safe)
val bounds = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(context).bounds
val bpp = if (preferredConfig == Bitmap.Config.RGB_565) 2 else 4
val windowSizeBytes = bounds.width().toLong() * bounds.height().toLong() * bpp
// B. Display Refresh Rate Lookahead
val refreshRate = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display?.refreshRate ?: 60f
} else {
@Suppress("DEPRECATION")
windowManager.defaultDisplay.refreshRate
}
val screenLookahead = when {
refreshRate >= 110f -> 3.0f
refreshRate >= 85f -> 2.5f
else -> 2.0f
}
// C. Grid Column Density Multiplier
val gridFactor = (1.0f + 0.10f * (activeGridColumns - 2)).coerceIn(1.0f, 1.30f)
// D. Calculate Target Surface Demand
val targetDemandBytes = (windowSizeBytes * screenLookahead * gridFactor * bandwidthMultiplier).toLong()
val operationalFloorBytes = (1.5f * windowSizeBytes).toLong()
// E. Proportional Heap Ceiling
val maxHeapBytes = Runtime.getRuntime().maxMemory()
val heapCapBytes = (maxHeapBytes * 0.20).toLong()
val totalRamCapBytes = (totalRamMb * 1024L * 1024L * 0.015).toLong()
val proportionalCeiling = min(heapCapBytes, totalRamCapBytes)
// F. Hardware Platform Cap
val hardwareCapBytes = hardwareCapMb * 1024L * 1024L
// G. Evaluate Formula
val boundedDemand = max(targetDemandBytes, operationalFloorBytes)
val effectiveCeiling = min(proportionalCeiling, hardwareCapBytes)
val fgCacheTarget = min(boundedDemand, effectiveCeiling) * exitMultiplier
return (appStateMultiplier * fgCacheTarget).toInt()
}
/**
* Read Closed-Loop Exit Telemetry (Android 11+)
*/
private fun checkHistoricalExitFeedback(): Float {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val exits = activityManager.getHistoricalProcessExitReasons(app.packageName, 0, 1)
if (exits.isNotEmpty()) {
val lastExit = exits[0]
val desc = lastExit.description ?: ""
if (desc.contains("MemoryLimiter:AnonSwap") ||
desc.contains("memory.high") ||
lastExit.reason == ApplicationExitInfo.REASON_OTHER) {
return 0.80f // Defensive 20% backoff
}
if (lastExit.reason == ApplicationExitInfo.REASON_LOW_MEMORY) {
return 0.85f // Low memory kill backoff
}
}
}
return 1.0f
}
/**
* Foldable Display Transition Handler (Debounced)
*/
@MainThread
fun onConfigurationChanged(activity: Activity, newConfig: Configuration, columns: Int) {
this.activeGridColumns = columns
val newTargetBytes = computeOptimalCacheSizeBytes(activity)
// If shrinking (unfolded -> folded or entering split screen), trim old bitmaps immediately
if (newTargetBytes < memoryCache.maxSize()) {
memoryCache.trimToSize(newTargetBytes)
}
memoryCache.resize(newTargetBytes)
}
// --- Process Lifecycle (App Visibility Multiplier) ---
override fun onStart(owner: LifecycleOwner) {
// App enters Foreground
appStateMultiplier = 1.00f
val target = computeOptimalCacheSizeBytes(app)
memoryCache.resize(target)
}
override fun onStop(owner: LifecycleOwner) {
// App enters Background: Evict 100% of memory cache to disk cache
// Completely eliminates risk of Android 17 cgroup v2 kill and Play Bad Behavior flags
appStateMultiplier = 0.00f
memoryCache.evictAll()
}
// --- System Memory Pressure (ComponentCallbacks2) ---
override fun onTrimMemory(level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// UI went to background
memoryCache.evictAll()
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
// Severe memory pressure: drop 75%
memoryCache.trimToSize(memoryCache.maxSize() / 4)
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
// Moderate memory pressure: drop 50%
memoryCache.trimToSize(memoryCache.maxSize() / 2)
}
}
}
override fun onLowMemory() {
memoryCache.evictAll()
}
override fun onConfigurationChanged(newConfig: Configuration) {
// Handled via Activity-specific onConfigurationChanged
}
companion object {
@Volatile private var instance: UnifiedAdaptiveCacheManager? = null
fun init(app: Application): UnifiedAdaptiveCacheManager =
instance ?: synchronized(this) {
instance ?: UnifiedAdaptiveCacheManager(app).also { instance = it }
}
fun get(): UnifiedAdaptiveCacheManager =
instance ?: error("UnifiedAdaptiveCacheManager must be initialized in Application.onCreate()")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment