mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 16:33:27 +00:00
Merge pull request #3503 from vitorpamplona/claude/appmodules-memory-trim-deprecations-7301bk
Adapt memory trimming to Android 14+ trim level changes
This commit is contained in:
@@ -155,13 +155,11 @@ class Amethyst : Application() {
|
||||
if (isNappletSandbox) return
|
||||
instance.trim(level)
|
||||
// Drop warm embedded tab sessions under genuine memory pressure (decision: keep warm until the
|
||||
// user or Android reclaims them). Deliberately NOT on UI_HIDDEN/BACKGROUND — those fire on every
|
||||
// backgrounding, and a pinned tab should survive that. Only on real pressure levels, R+ only.
|
||||
val pressure =
|
||||
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW ||
|
||||
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL ||
|
||||
level == ComponentCallbacks2.TRIM_MEMORY_MODERATE ||
|
||||
level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
|
||||
// user or Android reclaims them). Since API 34 the OS only delivers UI_HIDDEN and BACKGROUND:
|
||||
// BACKGROUND means the process is on the system LRU list (real reclaim pressure), while UI_HIDDEN
|
||||
// fires on every app switch — so evict only at BACKGROUND and above, letting a pinned tab survive
|
||||
// a plain backgrounding. R+ only.
|
||||
val pressure = level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
|
||||
if (pressure && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
EmbeddedTabHost.evictAll()
|
||||
}
|
||||
|
||||
@@ -912,61 +912,36 @@ class AppModules(
|
||||
trimmingService.run(loggedIn, LocalPreferences.allSavedAccounts(), level)
|
||||
// Trim in-process caches proportional to OS memory pressure.
|
||||
//
|
||||
// Background levels (app not visible, ordered highest-first so the when
|
||||
// chain short-circuits at the right tier):
|
||||
// COMPLETE (80) — at the bottom of the LRU list, kill imminent
|
||||
// MODERATE (60) — system is hurting, neighbouring apps being killed
|
||||
// BACKGROUND(40) — backgrounded, mild system pressure
|
||||
// UI_HIDDEN (20) — just backgrounded, no pressure yet
|
||||
// Since API 34 the OS only ever delivers two trim levels (the foreground
|
||||
// RUNNING_* levels and the deeper MODERATE/COMPLETE background tiers were
|
||||
// deprecated because apps are no longer notified of them):
|
||||
// BACKGROUND(40) — process is on the system LRU list: real reclaim
|
||||
// pressure, and the strongest signal we still get.
|
||||
// UI_HIDDEN (20) — just backgrounded, no pressure yet. Fires on EVERY
|
||||
// app switch.
|
||||
//
|
||||
// Foreground levels (app is active but system is low):
|
||||
// RUNNING_CRITICAL (15), RUNNING_LOW (10)
|
||||
//
|
||||
// UI_HIDDEN fires on EVERY app switch. Don't clear CPU-heavy caches
|
||||
// (Robohash SVG assembly, rich-text parsing) there — clearing them
|
||||
// forces a full rebuild on every resume and causes visible jank.
|
||||
// So we key off exactly those two. UI_HIDDEN is frequent, so it only trims
|
||||
// images (bitmaps are the largest allocations) and keeps the CPU-heavy
|
||||
// caches (Robohash SVG assembly, rich-text parsing) warm — clearing them
|
||||
// would force a full rebuild on every resume and cause visible jank.
|
||||
// BACKGROUND trims hard but keeps a small working set: it means "on the LRU
|
||||
// list" (real reclaim pressure), not the imminent kill that COMPLETE used to
|
||||
// signal — so leave just enough warm to redraw the screen the user left on.
|
||||
when {
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
|
||||
// Kill imminent: free everything.
|
||||
memoryCache.trimToSize(0)
|
||||
CachedRichTextParser.trimToSize(0)
|
||||
CachedRobohash.trimToSize(0)
|
||||
nip11Cache.trimToSize(0)
|
||||
}
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
|
||||
// System under real pressure: clear images and most parsed state.
|
||||
memoryCache.trimToSize(0)
|
||||
CachedRichTextParser.trimToSize(50)
|
||||
CachedRobohash.trimToSize(10)
|
||||
nip11Cache.trimToSize(100)
|
||||
}
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> {
|
||||
// Backgrounded with mild pressure: trim significantly.
|
||||
memoryCache.trimToSize(memoryCache.maxSize / 4)
|
||||
CachedRichTextParser.trimToSize(100)
|
||||
// On the LRU list under real pressure: trim hard, but keep a small
|
||||
// working set so a returning user doesn't rebuild the visible screen
|
||||
// from scratch. memoryCache is byte-sized (Coil), the rest are entry counts.
|
||||
memoryCache.trimToSize(memoryCache.maxSize / 10)
|
||||
CachedRichTextParser.trimToSize(10)
|
||||
CachedRobohash.trimToSize(20)
|
||||
nip11Cache.trimToSize(200)
|
||||
nip11Cache.trimToSize(10)
|
||||
}
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
|
||||
// Just backgrounded, no pressure yet: trim images (bitmaps are the
|
||||
// largest allocations) but keep parsed-text and avatar caches warm
|
||||
// so resuming is instant.
|
||||
// Just backgrounded, no pressure yet: trim images but keep the
|
||||
// parsed-text and avatar caches warm so resuming is instant.
|
||||
memoryCache.trimToSize(memoryCache.maxSize / 2)
|
||||
}
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
|
||||
// Foreground, critically low memory.
|
||||
memoryCache.trimToSize(memoryCache.maxSize / 4)
|
||||
CachedRichTextParser.trimToSize(100)
|
||||
CachedRobohash.trimToSize(20)
|
||||
nip11Cache.trimToSize(200)
|
||||
}
|
||||
level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
|
||||
// Foreground, low memory.
|
||||
memoryCache.trimToSize(memoryCache.maxSize / 2)
|
||||
CachedRichTextParser.trimToSize(250)
|
||||
CachedRobohash.trimToSize(50)
|
||||
nip11Cache.trimToSize(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-16
@@ -34,19 +34,18 @@ class MemoryTrimmingService(
|
||||
var isTrimmingMemoryMutex = AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Tiered pruning scaled to the OS memory-pressure level.
|
||||
* Two-tier pruning keyed to the OS trim levels still delivered since API 34
|
||||
* (the foreground RUNNING_* and deeper MODERATE/COMPLETE levels were deprecated
|
||||
* because apps are no longer notified of them).
|
||||
*
|
||||
* Tier 1 — mild pressure (UI hidden, running-moderate):
|
||||
* Tier 1 — UI hidden (fires on every app switch):
|
||||
* Sweep stale WeakRefs, drop expired and superseded-replaceable events.
|
||||
* Safe to run frequently; no UI-visible side effects.
|
||||
*
|
||||
* Tier 2 — low memory (running-low, background):
|
||||
* Tier 1 + old chat messages + unobserved thread replies / reactions.
|
||||
* May cause feeds to re-fetch content that was scrolled past.
|
||||
*
|
||||
* Tier 3 — critical / imminent kill (running-critical, moderate, complete):
|
||||
* Tier 2 + sever all observer links + drop every event from muted/blocked users.
|
||||
* Aggressive; triggers recomposition wherever StateFlows were cleared.
|
||||
* Tier 2 — background / real reclaim pressure (process on the LRU list):
|
||||
* Tier 1 + drop events from muted/blocked users + old chat messages +
|
||||
* unobserved thread replies / reactions. May cause feeds to re-fetch content
|
||||
* that was scrolled past; triggers recomposition wherever StateFlows cleared.
|
||||
*/
|
||||
private fun doTrim(
|
||||
account: Collection<Account>,
|
||||
@@ -60,16 +59,13 @@ class MemoryTrimmingService(
|
||||
cache.pruneExpiredEvents()
|
||||
cache.prunePastVersionsOfReplaceables()
|
||||
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
// Tier 2: medium pressure — drop events from muted/blocked users
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
|
||||
// Tier 2: real reclaim pressure — drop events from muted/blocked users, old
|
||||
// messages, and unobserved reactions.
|
||||
account.forEach {
|
||||
cache.pruneHiddenEvents(it)
|
||||
cache.pruneHiddenMessages(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// Tier 3: critical pressure — drop old messages and unobserved reactions
|
||||
val accounts = otherAccounts.mapNotNull { decodePublicKeyAsHexOrNull(it.npub) }.toSet()
|
||||
cache.pruneOldMessages()
|
||||
cache.pruneRepliesAndReactions(accounts)
|
||||
@@ -79,7 +75,7 @@ class MemoryTrimmingService(
|
||||
suspend fun run(
|
||||
account: Collection<Account>,
|
||||
otherAccounts: List<AccountInfo>,
|
||||
level: Int = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,
|
||||
level: Int = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
|
||||
) {
|
||||
if (isTrimmingMemoryMutex.compareAndSet(false, true)) {
|
||||
Log.d("ServiceManager", "Trimming Memory (level=$level)")
|
||||
|
||||
+3
-1
@@ -166,7 +166,9 @@ class PlaybackService : MediaSessionService() {
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// Since API 34 the OS only delivers UI_HIDDEN and BACKGROUND; BACKGROUND (process on
|
||||
// the system LRU list) is the real reclaim-pressure signal, so release the warm pool then.
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
|
||||
poolNoProxy?.exoPlayerPool?.releaseWarmPool()
|
||||
poolWithProxy?.exoPlayerPool?.releaseWarmPool()
|
||||
}
|
||||
|
||||
+4
-3
@@ -143,11 +143,12 @@ class AccountFeedContentStates(
|
||||
val webBookmarks = FeedContentState(WebBookmarkFeedFilter(account), scope, LocalCache)
|
||||
|
||||
init {
|
||||
// Under critical memory pressure, trim every feed down to 50 items to release
|
||||
// the strong Note references that would otherwise keep pruned cache objects alive.
|
||||
// Under real memory pressure (process on the system LRU list — the strongest trim
|
||||
// level the OS still delivers since API 34), trim every feed down to release the
|
||||
// strong Note references that would otherwise keep pruned cache objects alive.
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Amethyst.instance.trimLevelEvents.collect { level ->
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
|
||||
trimFeedsToSize(200)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user