Well, this sure enough confirmed my hypothesis of "if no one on stackexchange knows the answer, you're going to have to figure it out yourself".
So I did.
The answer to this question is twofold. There's the window manager and the activity manager, and both play a certain role in this.
While it was tempting to write a TL;DR at the top here, I didn't, because especially with the second part, you have to be very careful with what you're doing, at least for now.
Window Manager/Recent Tasks
Let's start with the window manager, as this will also be interesting for people without large amounts of RAM. Also, it's quite a bit simpler.
It works with a number of variables: config_activeTaskDurationHours
as well as a number of NumVisibleRecentTasks
variables. config_activeTaskDurationHours
determines, as the name suggests, how long a task is considered active, that is, relevant to the user. On my device, this was set to 6. After those 6 hours, the "task" no longer appears in the recent apps list, with one exception: the number of open apps would be lower than, in my case, config_minNumVisibleRecentTasks
, launcher included. In that case, the app is not discarded. If the number of open apps is the same as config_minNumVisibleRecentTasks
, though, then as soon as you open an app that isn't opened yet, the oldest open app that hasn't been used for more than 6 hours is discarded.
There are also a couple of other related settings that weren't relevant in my case:
config_minNumVisibleRecentTasks_grid
: This is for when apps are displayed in a grid. This view is not available on many builds of Android, though.
config_minNumVisibleRecentTasks_lowRam
: The same setting for low RAM devices.
config_maxNumVisibleRecentTasks
, config_maxNumVisibleRecentTasks_grid
, and config_maxNumVisibleRecentTasks_lowRam
: these are upper limits for the numbers of recent tasks. In my case config_maxNumVisibleRecentTasks
was set to -1, which means there is no maximum limit.
These are all grabbed from resources, and can be modified with GravityBox (root and XPosed/EdExposed/LSPosed required), under the Advanced tuning section, in the Framework section. They require a restart to activate the changes.
In my case, I set both config_activeTaskDurationHours
and config_minNumVisibleRecentTasks
to a ridiculous level: 5000. That way, it takes over a half a year for a task to have a chance to be removed, and that only if there are more than 5000 apps open, which I suspect will never happen.
Activity Manager/OomAdjuster
This was a bit of a tougher nut to crack for me, and took a good amount of searching through the source code to find the answer - although the answer may not have really required it. Oh, well.
I first tried writing an XPosed module to override com.android.server.am.ActivityManagerConstants
, specifically updateMaxCachedProcesses
. Unfortunately, I couldn't get it to hook the method properly, I don't know why. Mind you, I've never tried writing an XPosed module before, and I did this with AIDE - maybe sometime I'll unpack Android Studio again and try it in there. But if anyone wants finer control, this should offer an avenue to do that.
At the end, my original suspicion that you might be able to use the background process limit unoficially to increase the limit ended up being true. Sure enough, if you go into developer settings and change the background process limit to, say, 4, the output of su -c dumpsys activity settings
looks like this at the end:
mOverrideMaxCachedProcesses=4
CUR_MAX_CACHED_PROCESSES=4
CUR_MAX_EMPTY_PROCESSES=2
CUR_TRIM_EMPTY_PROCESSES=15
CUR_TRIM_CACHED_PROCESSES=10
Note that the trim levels aren't changed, and IMO it doesn't make sense to change those levels even if the device has a high amount of RAM. Essentially, if the process numbers are below the trim levels, the system doesn't even bother trimming memory.
So, how can we assign a higher value? This is where things get complicated. I tried to call ActivityManager.setProcessLimit()
with the permission android.permission.SET_PROCESS_LIMIT
(which is only available to system apps but can also be granted via ADB) as defined in the IActivityManager Interface, but I couldn't get it to work (again, I was working with AIDE, so that may be the problem, I may try it again on Android Studio later).
However, there is a little known android shell command, service call
, which essentially lets you call a whole bunch of methods from different service interfaces, see Where to find info on Android's "service call" shell command?. What you're looking for is the activity service, which is defined in android.app.IActivityManager.aidl
, and you're looking for the method setProcessLimit(int max)
. Using the terminal, we can do service list
to get a list of the services that can be called here. In my case, the service name I'm looking for was activity. Then, we look up the setProcessLimit(int max)
command in the source code. Since I'm reasonably close to AOSP, I can look it up in there. In my case, it's the 40th command.
WARNING: Before I show what I needed for my device, don't just blindly follow this. This is different for just about every version of Android, and if you mess it up, it could cause problems. Read the link from the paragraph beforehand to be sure, at the very least be sure to understand my explanation in the previous paragraph.
In my case, I decided to set it to something ridiculous, so I took the tenfold of the original value, 600. That turned it into: service call activity 40 i32 600
(be sure to run su
before that as this needs root privileges). Now, when I call su -c dumpsys activity settings
, the section looks like this:
mOverrideMaxCachedProcesses=600
CUR_MAX_CACHED_PROCESSES=600
CUR_MAX_EMPTY_PROCESSES=300
CUR_TRIM_EMPTY_PROCESSES=15
CUR_TRIM_CACHED_PROCESSES=10
Halleluja! We did it! Mind you, AFAIK, you'll have to repeat the service call
command on every restart, and if you mess around with the background process limit setting in developer settings, you'll also lose what you just set.
Maybe someday I'll try the approach with a root/adb app or an XPosed module again, and If I do, I'll update this answer. But for now, I'm happy with the results. RAM usage is now more between 5.5 and 6GB, as opposed to 4GB. Apps seem to be restarting less. Life is better.