Created
September 11, 2026 14:19
-
-
Save seventhmoon/ca29e2866d9fdb4ff8e6897a58d19d8b to your computer and use it in GitHub Desktop.
Cache Manager for Image Loaders
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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