Revert MemoryOptimizer — reclaim wasn't worth the footprint
The kill-background approach worked at startup (−1.6 GB) but respawns pulled most of that back within 1–3 min, and the periodic sweep only kept the first sweep's reclaim stable rather than going lower. Net practical benefit over "just let Android manage it" is small, not worth a custom optimizer + normal-perm + file maintenance. Removes: - MemoryOptimizer.kt - KazeiaService.onCreate calls to freeRamForModels / startPeriodicOptimizer - KILL_BACKGROUND_PROCESSES permission from the manifest Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
751e3e0868
commit
6a958c1a10
|
|
@ -9,13 +9,6 @@
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- Freeing RAM for the LLM/TTS/STT models at startup. Kazeia uses
|
|
||||||
~5 GB across Qwen3-4B PTE + Qwen3-TTS + Whisper, and the OnePlus
|
|
||||||
Pad's ColorOS base load alone keeps ~7 GB resident, pushing most
|
|
||||||
of Kazeia's weights into ZRAM swap. KILL_BACKGROUND_PROCESSES is
|
|
||||||
a normal permission (no user prompt) that lets us reclaim that
|
|
||||||
headroom by nudging non-essential apps out of RAM on launch. -->
|
|
||||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".KazeiaApplication"
|
android:name=".KazeiaApplication"
|
||||||
|
|
|
||||||
|
|
@ -442,15 +442,6 @@ class KazeiaService : Service() {
|
||||||
} else {
|
} else {
|
||||||
startForeground(NOTIFICATION_ID, createNotification())
|
startForeground(NOTIFICATION_ID, createNotification())
|
||||||
}
|
}
|
||||||
// Free RAM for the ML models BEFORE they start loading. The tablet
|
|
||||||
// is 16 GB but ColorOS base + Google services routinely hog ~7 GB
|
|
||||||
// on their own, pushing Qwen3-4B's 3.2 GB of weights into ZRAM
|
|
||||||
// swap and causing page faults on every inference. Nudging the
|
|
||||||
// background apps out (see MemoryOptimizer.KILL_TARGETS) typically
|
|
||||||
// reclaims 1.5 GB on first run, then re-killed processes respawn
|
|
||||||
// over 1–3 minutes so a periodic sweep keeps them contained.
|
|
||||||
MemoryOptimizer.freeRamForModels(this) { msg -> log(msg) }
|
|
||||||
MemoryOptimizer.startPeriodicOptimizer(this, serviceScope) { msg -> log(msg) }
|
|
||||||
initializeComponents()
|
initializeComponents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
package com.kazeia.service
|
|
||||||
|
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.content.Context
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees RAM for Kazeia's ML models by nudging non-essential background
|
|
||||||
* processes out of memory. Uses `ActivityManager.killBackgroundProcesses`
|
|
||||||
* which:
|
|
||||||
* - only kills processes that are NOT in foreground or bound to something
|
|
||||||
* foreground (so no user-visible disruption),
|
|
||||||
* - leaves the package installed/launchable — the OS will respawn it on
|
|
||||||
* demand if the user opens the app or a push arrives,
|
|
||||||
* - requires only `KILL_BACKGROUND_PROCESSES`, a normal permission.
|
|
||||||
*
|
|
||||||
* Call [freeRamForModels] once, early in KazeiaService.onCreate, BEFORE
|
|
||||||
* loading Qwen3-4B (which needs ~3.2 GB resident to avoid swap thrashing).
|
|
||||||
*
|
|
||||||
* The list below was picked by reading `dumpsys meminfo` on a fresh boot
|
|
||||||
* of a OnePlus Pad 3 (ColorOS 16): every package here is either a Google
|
|
||||||
* background feature Kazeia doesn't use (Wallet, Chromecast, YouTube
|
|
||||||
* prefetch, …) or an OPLUS feature that re-spawns harmlessly when needed.
|
|
||||||
* We deliberately avoid Play Services core, input method, launcher,
|
|
||||||
* system_server, systemui, surfaceflinger, audioserver, HAL services —
|
|
||||||
* killing any of those would break the UI or audio routing.
|
|
||||||
*/
|
|
||||||
object MemoryOptimizer {
|
|
||||||
|
|
||||||
private const val TAG = "MemoryOptimizer"
|
|
||||||
|
|
||||||
/** Packages safe to evict. Order matches approximate RAM savings. */
|
|
||||||
private val KILL_TARGETS = listOf(
|
|
||||||
// Google optional features
|
|
||||||
"com.google.android.googlequicksearchbox",
|
|
||||||
"com.google.android.youtube",
|
|
||||||
"com.google.android.apps.walletnfcrel",
|
|
||||||
"com.google.android.apps.chromecast.app",
|
|
||||||
"com.google.android.aicore",
|
|
||||||
"com.google.android.apps.messaging",
|
|
||||||
"com.google.android.apps.tachyon", // Google Meet/Duo (resp. 96 MB)
|
|
||||||
"com.google.android.gm",
|
|
||||||
"com.google.android.as",
|
|
||||||
"com.google.android.as.oss",
|
|
||||||
// Additional targets discovered as respawning heavy after the
|
|
||||||
// first kill sweep on OnePlus Pad 3 — all user-facing apps that
|
|
||||||
// we don't need while Kazeia is the active task.
|
|
||||||
"com.google.android.apps.photos",
|
|
||||||
"com.google.android.calendar",
|
|
||||||
"com.android.providers.calendar",
|
|
||||||
"com.google.android.contacts",
|
|
||||||
"com.android.vending", // Play Store (resp. 150 MB)
|
|
||||||
"com.google.android.rkpdapp",
|
|
||||||
"android.process.acore",
|
|
||||||
// OPLUS / ColorOS optional features
|
|
||||||
"com.oneplus.deskclock",
|
|
||||||
"com.coloros.smartsidebar",
|
|
||||||
"com.coloros.assistantscreen",
|
|
||||||
"com.coloros.weather.service",
|
|
||||||
"com.heytap.mcs",
|
|
||||||
"com.heytap.accessory",
|
|
||||||
"com.oplus.cosa",
|
|
||||||
"com.oplus.pantanal.ums",
|
|
||||||
"com.oplus.nhs",
|
|
||||||
"com.oplus.midas",
|
|
||||||
"com.oplus.olc",
|
|
||||||
"com.oplus.deepthinker",
|
|
||||||
"com.oplus.blur",
|
|
||||||
"com.oplus.statistics.rom",
|
|
||||||
"com.oplus.powermonitor",
|
|
||||||
"com.oplus.romupdate",
|
|
||||||
"com.oplus.location",
|
|
||||||
"com.oplus.gesture",
|
|
||||||
"com.oplus.appplatform",
|
|
||||||
"com.oplus.persist.multimedia",
|
|
||||||
"com.oplus.nas",
|
|
||||||
"com.oplus.notificationmanager",
|
|
||||||
"com.oplus.safecenter",
|
|
||||||
"com.oplus.securitypermission",
|
|
||||||
"com.oplus.sau",
|
|
||||||
// Qualcomm workload profiler — perf hints only, respawns
|
|
||||||
"com.qualcomm.qti.workloadclassifier"
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask ActivityManager to drop background processes for every package
|
|
||||||
* in [KILL_TARGETS] that is installed. Returns a (attempted, skipped)
|
|
||||||
* pair for logging. Safe to call from any thread.
|
|
||||||
*/
|
|
||||||
fun freeRamForModels(context: Context, log: (String) -> Unit = {}): Pair<Int, Int> {
|
|
||||||
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
|
||||||
?: run { log("$TAG: ActivityManager unavailable"); return 0 to 0 }
|
|
||||||
val pm = context.packageManager
|
|
||||||
val memBefore = readAvailMb(am)
|
|
||||||
|
|
||||||
var killed = 0
|
|
||||||
var skipped = 0
|
|
||||||
for (pkg in KILL_TARGETS) {
|
|
||||||
try {
|
|
||||||
// Only attempt for installed packages — unknown packages
|
|
||||||
// would silently no-op but still spam the audit log.
|
|
||||||
pm.getPackageInfo(pkg, 0)
|
|
||||||
am.killBackgroundProcesses(pkg)
|
|
||||||
killed++
|
|
||||||
} catch (_: android.content.pm.PackageManager.NameNotFoundException) {
|
|
||||||
skipped++
|
|
||||||
} catch (e: SecurityException) {
|
|
||||||
log("$TAG: killBackgroundProcesses($pkg) denied: ${e.message}")
|
|
||||||
skipped++
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log("$TAG: killBackgroundProcesses($pkg) failed: ${e.message}")
|
|
||||||
skipped++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the kernel a moment to reclaim pages before we report.
|
|
||||||
Thread.sleep(250)
|
|
||||||
val memAfter = readAvailMb(am)
|
|
||||||
log("$TAG: killed=$killed skipped=$skipped; avail RAM ${memBefore} MB → ${memAfter} MB (+${memAfter - memBefore} MB)")
|
|
||||||
return killed to skipped
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readAvailMb(am: ActivityManager): Long {
|
|
||||||
val info = ActivityManager.MemoryInfo()
|
|
||||||
am.getMemoryInfo(info)
|
|
||||||
return info.availMem / (1024 * 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kick off a background coroutine that re-runs [freeRamForModels]
|
|
||||||
* every [periodMs]. ColorOS's ActivityManager auto-respawns killed
|
|
||||||
* apps within 1–3 minutes (observed: quicksearchbox coming back at
|
|
||||||
* 210 MB, photos/calendar/vending spawning fresh), so a single
|
|
||||||
* startup sweep isn't enough — periodic pruning is needed while
|
|
||||||
* Kazeia holds its models in memory.
|
|
||||||
*
|
|
||||||
* Cheap (~100 ms total per sweep, mostly the intentional 250 ms
|
|
||||||
* settle sleep in freeRamForModels), so 60 s cadence is safe. The
|
|
||||||
* returned [kotlinx.coroutines.Job] should be cancelled by the
|
|
||||||
* caller when the service stops, to avoid waking the CPU after
|
|
||||||
* shutdown.
|
|
||||||
*/
|
|
||||||
fun startPeriodicOptimizer(
|
|
||||||
context: Context,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
periodMs: Long = 60_000L,
|
|
||||||
log: (String) -> Unit = {}
|
|
||||||
): Job = scope.launch(Dispatchers.IO) {
|
|
||||||
// First run already happened at service onCreate — skip the
|
|
||||||
// initial interval so we don't double-sweep on startup.
|
|
||||||
delay(periodMs)
|
|
||||||
while (currentCoroutineContext()[Job]?.isActive != false) {
|
|
||||||
try {
|
|
||||||
freeRamForModels(context, log)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log("$TAG: periodic sweep failed: ${e.message}")
|
|
||||||
}
|
|
||||||
delay(periodMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue