From 0fb0c7fb4c1881ed4273c2cd5fc84383d94a279c Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:10:27 +0200 Subject: [PATCH] perf(android): cap engine memory pools on low-RAM devices Flutter's imageCache budget only bounds decoded CPU bitmaps: Skia's GPU resource cache is sized from the surface area (hundreds of MB on a 4K-composited TV) and the Dart old gen defaults to a large fraction of physical RAM. Both drive LMK kills on 2GB boxes. Cap them via engine shell args on hardware matching the reduced-tier triple. Ref #1349 --- .../kotlin/com/edde746/plezy/MainActivity.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt index 0123153c..8584da05 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt @@ -51,6 +51,10 @@ class MainActivity : FlutterActivity() { private const val TEXT_INPUT_DIAGNOSTICS_ENABLED = false private const val EXTERNAL_PLAYER_REQUEST_CODE = 7461 + // Mirrors DevicePerformance._lowMemThresholdBytes (2252 MiB): nominal + // "2GB" devices report totalMem slightly above 2 GiB after carve-outs. + private const val LOW_MEM_THRESHOLD_BYTES = 2252L shl 20 + // External player result APIs used by Jellyfin Android TV. private const val API_MX_RETURN_RESULT = "return_result" private const val API_MX_RESULT_ID = "com.mxtech.intent.result.VIEW" @@ -210,6 +214,18 @@ class MainActivity : FlutterActivity() { ) } + /** + * Same triple DevicePerformance uses for the reduced tier on the Dart + * side — keep the two in sync. Evaluated here too because engine shell + * args must be decided before Dart runs. + */ + private fun isLowRamClass(): Boolean { + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val memoryInfo = ActivityManager.MemoryInfo() + activityManager.getMemoryInfo(memoryInfo) + return !Process.is64Bit() || activityManager.isLowRamDevice || memoryInfo.totalMem <= LOW_MEM_THRESHOLD_BYTES + } + /** User-assigned device name (Settings > About > Device name), or null. */ private fun getDeviceName(): String? { // The name the user gave the device; also used by Cast/Nearby. @@ -382,6 +398,15 @@ class MainActivity : FlutterActivity() { val args = super.getFlutterShellArgs() usingSkia = shouldDisableImpeller() if (usingSkia) args.add("--enable-impeller=false") + if (isLowRamClass()) { + // Bound the memory pools Dart can't reach: Skia's GPU resource cache + // is sized from the surface area (hundreds of MB on a 4K-composited + // TV) and the Dart old gen defaults to a large fraction of physical + // RAM. Both drive LMK kills on 2GB boxes (#1349). + if (usingSkia) args.add("--resource-cache-max-bytes-threshold=50331648") + args.add("--old-gen-heap-size=256") + Log.i(TAG, "Low-RAM device: capped engine caches (skia=$usingSkia, oldGen=256MB)") + } return args }