This is the first day of my participation in the First Challenge 2022.

Relearning Android: 3 Myths about the Activity Life Cycle was posted by Kang KunMinX yesterday.

In single-process scenarios, an Activity can only be reclaimed because the process was reclaimed by the system.

Doesn’t feel right? Because a long time ago, there was this scene:

The App opens multiple activities, and then the phone is put aside. After a period of time (the screen is steady on), click back, and the previous Activity is blank, and then the phone is reloaded.

If the App is in the foreground, the Activity that is not at the top of the stack is killed, but the process is still alive.

Write a code to verify it immediately, and the process is as follows:

Create a parent Activity, lifecycle callback and log print, and then start an Activity with a button that opens multiple activities in sequence. Add a button to the last one, and click on it to request a larger ByteArray to simulate memory allocation. See if the Activity is reclaimed when out of memory.

The test results are as follows:

Android :largeHeap=”true”; android:largeHeap=”true”;

em… Did I just remember wrong??

Wait!! I seem to be confusing two things: not enough available memory on the system and not enough available memory on the application.

0x1 The available memory is insufficient

LMK mechanism

In The Android system, the process life cycle is controlled by the system. For the sake of experience and performance, clicking the Home button or Back in the APP will not actually kill the APP. The process will still be stored in memory, so that the next time you start the APP, it will be faster. As the running time of the system increases, more and more APPS are opened, and the number of processes in the memory increases, the available memory of the system will become less and less. When the available memory reaches a threshold, the system will kill part of the process according to the priority of the process to release the memory for the subsequent startup of the APP.

This collection mechanism of Android is based on the OOM rule of the Linux kernel. It is called Low Memory Killer, or LMK for short.

Threshold & who to kill

This is done by combining the following two files, the value may be different for different phones, take my old meizlan E2 as an example (Android 11 Mix2S always said that I did not have permission to open this file) :

# /sys/module/lowmemorykiller/parameters/minfree
# unit: Page Page, 1Page = 4KB
18432,23040,27648,46080,66560,97280

# /sys/module/lowmemorykiller/parameters/adj
0,58,117,176,529,1000
Copy the code

The Android system maintains a priority of adj for each process:

  • Android 6 and formerly called: oOM_adj, value range: [-17,16], LMK to convert *1000/17
  • Android 7: oOM_score_adj, value range: [-1000,1000]

However, the values of the above two files are actually one-to-one, such as:

66560 x 4/1024 = 260MB → When the available memory is reduced to 260MB, processes whose adj value is greater than 529 will be killed. 18432 * 4/1024 = 72MB → when the available memory is reduced to 72MB, kill the process whose ajd value is greater than 0.Copy the code

How can adj

View the following information directly from the command line:

As you can see, ADJ changes dynamically, changing its value whenever the App state and the four component life cycles change. Common ADJ levels are as follows:

  • NATIVE_ADJ → -1000, init process fork native process, is not controlled by system;
  • System_ad_server → -900;
  • PERSISTENT_PROC_ADJ → -800, persistent_proc_n → -800, persistent_proc_n → -800
  • PERSISTENT_SERVICE_ADJ → -700, which is associated with a system or persistent process;
  • FOREGROUND_APP_ADJ → 0, foreground process;
  • VISIBLE_APP_ADJ → 100, visible process;
  • PERCEPTIBLE_APP_ADJ → 200, can perceive the process, such as background music playing;
  • BACKUP_APP_ADJ → 300, the backup process executing bindBackupAgent();
  • HEAVY_WEIGHT_APP_ADJ → 400, heavy weight process, set in system/rootdir/init.rc;
  • SERVICE_ADJ → 500, service process;
  • HOME_APP_ADJ → 600, Home process, type of ACTIVITY_TYPE_HOME application, such as Launcher;
  • PREVIOUS_APP_ADJ → 700, the last App process used by the user;
  • SERVICE_B_ADJ → 800, B List of services;
  • CACHED_APP_MIN_ADJ → 900, minimum adj for invisible processes;
  • CACHED_APP_MAX_ADJ → 906, maximum adj for invisible processes;
  • UNKNOWN_ADJ – 1001, generally refers to the process will be cache, without access to determine value;

Gityuan’s “Read the Android process priority ALGORITHM”, along with the summary of a wave of process survival tricks:

  • The UI process is separated from the Service process, and the Service process that contains the Activity may be recovered by the system at any time. If the Service process is separated from the system, the UI process must be separated to avoid large memory consumption.
  • If you really need a user aware application, call startForegroundService() to enable the foreground service, ADJ=200;
  • When the Service in the process is complete, you must actively call stopService or stopSelf to stop the Service to avoid occupying memory and wasting system resources.
  • Do not bind services or providers of other processes for a long time. Release them immediately after each use to prevent other processes from residing in the memory.
  • APP should implement interface onTrimMemory() and onLowMemory(), and release non-essential memory in callback method appropriately according to TrimLevel. When the system memory is tight, this interface will be called back to reduce the frequency of system stashing and killing processes.
  • More efforts should be made in the optimization of memory, the same ADJ level, the system will first kill memory occupation process;

Q: Can you make your App unkillable by setting adj. to -1000? A: No, you need to have root permission to modify adj.

Going a little further, but back to the question:

When the system runs out of memory, it kills processes directly in the kernel layer, rather than nagging you about which Activity to recycle in the Framework layer.

So at the system level, in a single-process scenario, an Activity can only be reclaimed because the process was reclaimed by the system, which is true at the application level.


The available memory of the application is Insufficient

The memory allocation of APP processes (VMS) is actually the allocation and release of the heap. To control the memory of the entire system, a heap limit threshold is set for each application. If an application tries to allocate memory close to the threshold, it will easily cause OOM.

The virtual machine has its own GC, so we don’t need to go to the specific GC algorithm

Assuming that running out of app memory does reclaim activities, how do you design it? One solution is as follows:

When the application starts, a child thread is started that periodically polls the current available memory to see if it exceeds the threshold and kills the Activity if it does

Is Android designed this way?

Activity Recycling mechanism

ActivityThread → main()

With the attach () :

ReleaseSomeActivities (), like the run() calculation: used memory > 3/4 maximum memory, then releaseSomeActivities() :

So the getService () is obtained IActivityTaskManager aidl interface, specific implementation class is ActivityTaskManangerService:

Continue with down: RootActivityContainer – releaseSomeActivitiesLocked () :

With: WindowProcessController – getReleaseSomeActivitiesTasks ()

Then go down is the release of the Activity code: ActivityStack – releaseSomeActivitiesLocked ()

Specific how to release, do not follow down, then follow down is how to monitor ~

Memory monitoring mechanism

With back: BinderInternal addGcWatcher ()

This might be a little confusing, but it’s great when you understand:

Virtual machine GC will kill the Object of WeakReference and call Finalize () of the object before releasing the memory. Here, a new WeakReference instance is created. The next GC will go through the code again, and gee, gee, it’s much more efficient than polling

At this point, the application’s out-of-memory recycling Activity process is roughly clear, and you can then write code to verify that this is true.

The Demo verification

Try two tasks first:

Simulate the memory allocation page, and then keep clicking ~

Would rather OOM than recycle, try three ~

OnDestory () : onCreate() : onCreate() : onDestory() : onDestory() : onDestory() : onDestory() :

Yes, only one Task is reclaimed at a time

0 x3, conclusion

  • When the system runs out of memory, processes are killed (reclaimed) directly at the kernel level, regardless of which Activity is reclaimed.
  • When the process memory is insufficient, if the number of Activity tasks in the process is greater than or equal to 3 and the memory usage exceeds 3/4, invisible tasks are reclaimed. One Task is reclaimed each time.

References:

  • Read the Android process priority adj. algorithm

  • LowMemoryKiller mechanism in Android

  • The LowMemoryKiller principle for Android process recycling

  • TaskRecord destruction analysis for Android Visible APP