De nombreuses applications effectuent des tâches légitimes lorsque leur UI n'est plus visible, comme le téléchargement de packs d'éléments volumineux, la synchronisation de bases de données locales, la diffusion de contenus audio, l'enregistrement d'exercices physiques ou le maintien d'une session d'appareil connecté. Lorsque ce travail en arrière-plan s'exécute dans le même processus d'OS que l'UI de premier plan et le moteur graphique, l'ensemble du processus reste à un niveau d'importance élevé (oom_score_adj entre 0 et 250), ainsi que toute la mémoire allouée par l'UI.
Cette page explique pourquoi cela nuit à votre application ainsi qu'au reste de l'appareil, comment déplacer les tâches en arrière-plan vers un processus léger dédié à l'aide de android:process, et comment vérifier les économies de mémoire à l'aide de adb shell et de Perfetto.
Pourquoi le travail sur un seul processus conserve la mémoire de l'UI
Android gère la mémoire et l'arrêt des processus au niveau de précision d'un processus d'OS (pid), et non des composants individuels (Activity, Service ou BroadcastReceiver). Le gestionnaire d'activités (OomAdjuster) attribue à chaque processus une seule valeur oom_score_adj qui reflète le composant actif ou la liaison entrante la plus importante de ce processus :
- Lorsque l'utilisateur interagit avec votre
Activity, le processus s'exécute à l'étatTOP(oom_score_adj = 0). - Lorsque l'utilisateur quitte l'application et que celle-ci ne comporte aucun composant actif en arrière-plan, le processus passe au bucket
PREV(oom_score_adj = 700) ouCACHED(oom_score_adj >= 900). Dans le bucket mis en cache, la plate-forme fige le processus avec le congélateur cgroup v2, compacte sa mémoire avecCachedAppOptimizeret laisselmkdarrêter le processus en premier en cas de pression de la mémoire. - Toutefois, si le même processus exécute également un service de premier plan (comme les types
dataSync,mediaPlayback,connectedDeviceoulocation, àoom_score_adj = 200) ou héberge un service auquel le système ou un autre client important est lié, l'ensemble du processus conserve cette priorité élevée même après l'arrêt deActivity.
Lorsqu'un processus d'UI lourd reste à oom_score_adj = 200, plusieurs grandes catégories de mémoire restent résidentes :
Activityarrêté et hiérarchie des vues : sauf si l'Activityest explicitement terminé, sonWindow, son arborescence de vues, ses structures de mise en page et son état de vue restent accessibles sur le tas Java lorsque l'utilisateur se trouve dans une autre application.- Contextes graphiques natifs et allocations de GPU : l'
HardwareRendererstandard conserve son contexte et ses caches de GPU, tandis qu'uneActivityarrêtée réside dans un processus qui n'est pas mis en cache. Les applications et les moteurs de jeu qui créent leurs propres contextes Vulkan (VkDevice,VkSwapchainKHR, pools de descripteurs, caches de pipeline) ou OpenGL ES (EGLContext, textures, tampons de sommets) conservent également leurs structures de pilote, leurs programmes de nuanceur et leurs tampons graphiques, sauf si le moteur détruit explicitement l'appareil graphique lorsqu'il est arrêté. - Fragmentation du tas de mémoire et surcharge d'exécution : même lorsqu'une application libère des caches dans
onTrimMemory, les pages d'allocateur natif partiellement remplies, les pages de copie sur écriture Zygote modifiées, les bibliothèques partagées chargées (.so) et les métadonnées d'exécution restent résidentes dans la mémoire privée du processus. Comme le processus est au-dessus du seuil de mise en cache (oom_score_adj < 900), la plate-forme ne le fige pas et n'exécute pas de compactage d'application mise en cache. Pour en savoir plus sur cet effet avec les services liés, consultez Colocaliser un service lié avec une UI gourmande en mémoire.
Comment un processus en arrière-plan léger protège votre application
Le fait de conserver une mémoire de taille de premier plan dans un état d'arrière-plan met en danger votre propre travail en arrière-plan :
- Les limites de mémoire en arrière-plan sont plus strictes que celles au premier plan. Sous Android 17 et versions ultérieures, le limiteur de mémoire applique une limite de mémoire non visible plus petite à votre application lorsqu'aucun de ses processus n'affiche d'UI visible, y compris lorsqu'un service de premier plan est en cours d'exécution. La limite couvre tous les processus de votre application. Si le processus qui exécute votre service de premier plan conserve toujours sa mémoire graphique et d'interface utilisateur, cette mémoire est comptabilisée dans la limite inférieure. Lorsque votre application dépasse la limite, le kernel la limite tout en récupérant ses pages, ce qui ralentit le téléchargement, la synchronisation ou la lecture en cours. Si votre application continue d'allouer de la mémoire une fois l'espace d'échange épuisé, le système l'arrête. Pour vérifier si votre application a été affectée, consultez Limites de mémoire de l'application.
- Les processus plus volumineux sont arrêtés en premier en cas de pression sur la mémoire. Lorsque
lmkddoit libérer de la mémoire et est configuré pour choisir le processus le plus lourd dans un bucket d'importance (ro.lmk.kill_heaviest_task), un processus de service de premier plan qui contient également des centaines de Mo de mémoire d'UI est arrêté avant les processus plus petits de même importance, et votre travail en arrière-plan s'arrête avec lui. Consultez Interaction entre kswapd et lmkd. - L'état de l'UI mis en cache peut être figé, compacté et réécrit dans la mémoire flash. En permettant à l'état de l'UI au premier plan de passer à un processus mis en cache, l'OS peut figer le processus, compacter sa mémoire en zRAM et réécrire ses pages froides dans l'espace de stockage flash. Son espace mémoire résident diminue tandis que l'état de l'UI reste intact, ce qui donne à votre travail en arrière-plan et à votre UI mise en cache les meilleures chances de survivre jusqu'au retour de l'utilisateur.
- Les budgets de mémoire par processus deviennent pratiques. Un processus qui n'exécute que des tâches en arrière-plan dispose d'un ensemble de travail réduit et prévisible. Vous pouvez donc lui attribuer un budget mémoire limité sans contraindre votre UI.
L'exécution d'un deuxième processus peut sembler augmenter l'espace mémoire total utilisé par votre application, car un deuxième environnement d'exécution entraîne un coût de référence faible. Cette comparaison n'est valable que si un seul processus peut libérer complètement l'ensemble de son UI et de son état graphique chaque fois qu'il passe en arrière-plan, ce que les applications et les kits d'outils d'UI ne peuvent pas faire de manière fiable en pratique. Laisser l'OS figer, compacter et réécrire un processus d'UI mis en cache distinct utilise beaucoup moins de mémoire physique dans le cas général, maintient votre application dans la limite de non-visibilité et réduit les arrêts pour mémoire insuffisante pour votre application et sur l'appareil.
Isoler les tâches en arrière-plan dans un processus dédié
Pour éliminer la résidence de l'UI et des graphismes pendant les tâches en arrière-plan, exécutez les composants d'arrière-plan de longue durée dans un processus léger dédié, tout en conservant l'UI et le moteur graphique dans le processus principal :
| Processus | Déclaration du fichier manifeste | Composants hébergés | État lorsque l'utilisateur quitte l'UI pendant une tâche en arrière-plan |
|---|---|---|---|
| Processus d'UI principal | Par défaut (com.example.app) |
Activity, vues de l'interface utilisateur, caches d'images, moteur de rendu Vulkan ou OpenGL ES, moteur audio et de scène
|
Chute à PREV, puis à CACHED (oom_score_adj >= 900). Gelé, compacté et réécrit dans la mémoire flash.
|
| Processus de nœud de calcul en arrière-plan | android:process=":bg" |
Service au premier plan, téléchargeur d'éléments en arrière-plan, worker de synchronisation ou gestionnaire d'appareils connectés
|
S'exécute à FGS (oom_score_adj = 200) avec uniquement le tas de mémoire et le code du worker, sans vues d'UI ni contextes de GPU.
|
Déclarer les budgets de processus et de mémoire
Attribuez votre tâche en arrière-plan Service à un sous-processus privé en définissant android:process=":bg" dans AndroidManifest.xml. Le signe deux-points (:) au début crée un processus privé limité à l'UID de votre application.
Sous Android 17 QPR2 (niveau d'API 37.2) et versions ultérieures, combinez la division des processus avec des budgets de mémoire d'application par processus dans le bloc <processes> afin que le système d'exploitation applique un plafond strict de l'ensemble de travail au worker en arrière-plan sans limiter le processus d'UI au premier plan :
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".HeavyApplication"
android:label="@string/app_name">
<!-- App-wide budgets that cover every process, including :bg -->
<memory-budget android:maxMb="512" />
<memory-budget
android:maxMb="128"
android:state="background" />
<processes>
<!-- Tight budget for the headless background worker process -->
<process android:process=":bg">
<memory-budget android:maxMb="32" />
</process>
</processes>
<activity
android:name=".MainActivity"
android:exported="true" />
<service
android:name=".AssetDownloadService"
android:process=":bg"
android:foregroundServiceType="dataSync"
android:exported="false" />
</application>
</manifest>
L'utilisation de la mémoire dans :bg est comptabilisée à la fois dans son propre budget et dans le budget de l'application. Sur les appareils équipés de versions antérieures à Android 17 QPR2, le système ignore <memory-budget>, tandis que android:process=":bg" fonctionne sur tous les niveaux d'API.
Initialisation de la porte d'accès dans Application.onCreate
Android instancie votre classe Application et appelle Application.onCreate dans chaque processus généré pour votre package. Une erreur courante lors de l'adoption de android:process=":bg" consiste à laisser des kits d'outils d'interface utilisateur lourds, des pools de chargement d'images, des SDK d'analyse ou l'initialisation du moteur natif (System.loadLibrary) dans une méthode Application.onCreate non protégée. Cela gonfle le processus :bg avec la même empreinte de référence que vous essayez d'éviter.
Vérifiez Application.getProcessName, disponible sur Android 9 (niveau d'API 28) ou version ultérieure, dans onCreate, et n'initialisez que les dépendances requises par le processus actuel :
Kotlin
class HeavyApplication : Application() {
override fun onCreate() {
super.onCreate()
val processName = Application.getProcessName()
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess()
return
}
initMainUiProcess()
}
private fun initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private fun initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni")
}
companion object {
private const val BG_PROCESS_SUFFIX = ":bg"
}
}
Java
public final class HeavyApplication extends Application {
private static final String BG_PROCESS_SUFFIX = ":bg";
@Override
public void onCreate() {
super.onCreate();
String processName = Application.getProcessName();
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess();
return;
}
initMainUiProcess();
}
private void initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private void initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni");
}
}
Les bibliothèques qui s'initialisent via un fournisseur de contenu, comme Jetpack App Startup, ne s'initialisent que dans le processus principal, car un fournisseur de contenu déclaré sans android:process s'exécute dans le processus principal. Si le code de :bg dépend d'une telle bibliothèque, initialisez-la explicitement dans initBackgroundWorkerProcess.
Si vous utilisez un moteur de jeu tel que Unity, implémentez le service d'arrière-plan dans une bibliothèque ou un plug-in Android standard écrit en Java ou Kotlin qui ne fait pas référence à des classes de moteur telles que UnityPlayer. Le processus :bg ne charge alors jamais les bibliothèques natives du moteur. Pour confirmer, vérifiez que adb shell cat /proc/<pid>/maps pour le processus :bg ne liste pas libunity.so ou libil2cpp.so.
Communiquer sans épingler le processus d'UI
Lorsque l'utilisateur a ouvert MainActivity pendant que AssetDownloadService s'exécute dans :bg, l'interface utilisateur doit souvent afficher la progression du téléchargement en direct. L'importance du processus passe d'un client au processus qui héberge le service ou le fournisseur de contenu qu'il utilise. La direction de chaque dépendance est donc importante :
- La liaison de l'UI au worker est sécurisée. Lorsque
MainActivityest lié àAssetDownloadService, le système élève:bgau niveau d'importance de l'UI visible. Lorsque l'utilisateur quitte l'application, le processus d'UI est mis en cache et la liaison n'élève plus rien. - Le worker dépendant du processus d'interface utilisateur n'est pas sécurisé. Si le code de
:bgest lié à un service ou interroge un fournisseur de contenu qui s'exécute dans le processus principal, le système élève le processus principal au niveau d'importance du nœud de calcul, et la mémoire de l'UI reste épinglée. Demandez plutôt à l'UI d'enregistrer un rappel auprès du nœud de calcul, par exemple unMessengerou une interface de rappel AIDL. La transmission d'un objet binder en tant que rappel ne modifie pas l'importance de l'un ou l'autre processus.
Annulez l'enregistrement du rappel avant d'annuler la liaison, afin que le nœud de calcul n'appelle jamais le processus d'UI après que le système l'a figé. L'exemple suivant n'effectue la liaison au nœud de calcul que lorsque l'UI est visible :
Kotlin
class MainActivity : Activity() {
private var downloadService: IBinder? = null
private val downloadConnection = object : ServiceConnection {
override fun onServiceConnected(
name: ComponentName,
service: IBinder
) {
downloadService = service
// Register a Messenger or AIDL callback for progress updates.
}
override fun onServiceDisconnected(name: ComponentName) {
// The binding stays active and reconnects if the worker restarts.
downloadService = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent(this, AssetDownloadService::class.java)
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection)
}
}
Java
public final class MainActivity extends Activity {
private IBinder downloadService;
private final ServiceConnection downloadConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(
ComponentName name, IBinder service) {
downloadService = service;
// Register a Messenger or AIDL callback for progress
// updates.
}
@Override
public void onServiceDisconnected(ComponentName name) {
// The binding stays active and reconnects if the worker
// restarts.
downloadService = null;
}
};
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, AssetDownloadService.class);
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null;
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection);
}
}
Pour en savoir plus sur l'impact des options de liaison sur oom_score_adj dans les processus, consultez Liaisons de services et états des processus.
Coordonner l'état partagé sur le disque de manière sécurisée
Étant donné que le processus principal et :bg s'exécutent dans des espaces d'adressage distincts avec des environnements d'exécution et des descripteurs de fichiers distincts :
- SQLite et Room : utilisez la journalisation WAL (mode de journalisation par défaut dans Room sur la plupart des appareils) pour que les deux processus puissent lire et écrire simultanément dans la base de données. N'appelez pas
enableMultiInstanceInvalidationdepuis:bg. Room héberge son service d'invalidation dans le processus principal. Le nœud de calcul se lie donc au processus principal et maintient la mémoire de l'UI au niveau d'importance élevé du nœud de calcul. À la place, faites en sorte que le worker signale les modifications via le rappel enregistré par l'UI, et que l'UI interroge à nouveau la base de données au démarrage. - Préférences : ne partagez pas
SharedPreferencesentre les processus. Utilisez DataStore dans un code multiprocessus ou transmettez l'état explicitement via IPC. - Fichiers d'éléments : écrivez les packs d'éléments téléchargés dans un fichier de préproduction dans
:bg, appelezfdatasync, puisposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)dans:bgaprès l'écriture (comme indiqué dans Renvoyer les pages de mémoire à l'OS), puis renommez le fichier de manière atomique. Laissez l'UI récupérer le résultat lors de son prochain démarrage et de sa liaison au worker, plutôt que d'appeler un processus d'UI qui pourrait être figé.
Observer l'effet dans l'interface système adb
Vous pouvez vérifier que la séparation de vos processus de premier plan et d'arrière-plan isole l'utilisation élevée de la mémoire en inspectant les états des processus d'Activity Manager et dumpsys meminfo après avoir accédé à l'écran d'accueil. L'exemple de résultat de cette section provient de l'application exemple MemoryLab, qui exécute la même allocation native de 64 Mo dans son processus d'interface utilisateur dans les deux configurations et héberge son service de premier plan fractionné dans un processus :remote.
1. Comparer les états des processus et les scores OOM
Démarrez la charge de travail en arrière-plan, appuyez sur le bouton d'accueil pour que Activity s'arrête, puis interrogez Activity Manager :
adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"
Dans une architecture à processus unique, le processus principal reste élevé même si son Activity est arrêté. Pendant environ 15 secondes après le départ de l'utilisateur, Activity Manager conserve un processus qui exécute un service de premier plan à oom_score_adj = 50 (fg-service-act), puis le réduit à 200 (fg-service). Le résultat suivant a été capturé 20 secondes après avoir appuyé sur Accueil :
Proc #1: prcp F/S/FGS ---NFUATI t: 0 13794:com.android.memorylab/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
Dans une architecture à processus fractionné, seul le processus de nœud de calcul reste à oom_score_adj = 200, tandis que le processus d'interface utilisateur principal passe à PREV (oom_score_adj = 700), puis au bucket mis en cache (oom_score_adj >= 900) :
Proc #3: prcp F/S/FGS ---NFUATI t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
Proc #1: prev b/ /LAST -------TI t: 0 13312:com.android.memorylab/u0a284 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Une fois le délai avant expiration de l'application précédente écoulé, le processus principal passe au bucket mis en cache (cch, oom_score_adj = 900, previous-expired) et perd toutes ses fonctionnalités de processus (---------), y compris le temps CPU implicite (I). À ce stade, le système le fige et peut le compacter ou le récupérer à tout moment sans interrompre le processus de nœud de calcul.
2. Comparer dumpsys meminfo entre les processus
Comparez le résumé dumpsys meminfo du nœud de calcul élevé au processus de l'interface utilisateur :
adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app
Exemple de sortie App Summary pour le processus de nœud de calcul MemoryLab à l'adresse oom_score_adj = 200 :
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 380 13840
Native Heap: 1548 2832
Code: 132 55816
Stack: 200 208
Graphics: 0 0
Private Other: 496
System: 1926
Unknown: 4168
TOTAL PSS: 4682 TOTAL RSS: 76864 TOTAL SWAP PSS: 441
Exemple de sortie App Summary pour le processus d'interface utilisateur MemoryLab à l'adresse oom_score_adj = 700, qui contient l'allocation native de 64 Mo dans Private Other :
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 1316 19816
Native Heap: 30408 31596
Code: 3148 115796
Stack: 656 664
Graphics: 0 0
Private Other: 67044
System: 13687
Unknown: 71092
TOTAL PSS: 116259 TOTAL RSS: 238964 TOTAL SWAP PSS: 259
Le processus de nœud de calcul nécessite moins de 5 Mo de PSS, car il partage la plupart de ses pages de code et d'exécution avec Zygote. Dans une application qui s'affiche avec Vulkan ou OpenGL ES, la ligne Graphics du processus d'UI inclut également la mémoire GPU.
Cet exemple a été capturé sur un appareil virtuel Cuttlefish avec un rendu logiciel. Par conséquent, Graphics est 0 dans les deux processus.
3. Inspecter la comptabilité cgroup par processus
Chaque processus sous votre UID possède son propre répertoire cgroup v2 dans un cgroup parent pour votre application. La commande suivante liste oom_score_adj, memory.current et le memory.high à l'échelle de l'application pour chaque processus de votre application :
adb shell '
for PID in $(pidof com.example.app com.example.app:bg); do
CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
"adj=$(cat /proc/$PID/oom_score_adj)" \
"memory.current=$(cat $CG/memory.current)" \
"app.memory.high=$(cat ${CG%/*}/memory.high)"
done
'
Exemple de résultat pour MemoryLab après avoir appuyé sur "Accueil" :
pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072
Le processus de calcul est facturé moins de 4 Mo, tandis que le processus d'interface utilisateur est facturé environ 98 Mo. Chaque processus possède son propre memory.current, mais le limiteur de mémoire définit sa limite sur le cgroup parent qui contient tous les processus de votre application. Les deux lignes affichent la même valeur app.memory.high de 966 787 072 octets, qui correspond à la limite non visible de 922 Mo dans le résultat suivant.
Pour afficher les limites visibles et non visibles que le limiteur de mémoire applique sur l'appareil, exécutez la commande suivante :
adb shell am memory-limiter status
Exemple de résultat d'un appareil doté de 4 Go de RAM, où la limite non visible est la moitié de la limite visible :
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=1843MB memMax=2048MB swapMax=1024MB
not-visible: memHigh=922MB memMax=1024MB swapMax=1024MB
Observer l'effet dans Perfetto
Perfetto vous permet de visualiser la chronologie de oom_score_adj et de RSS dans les deux processus lorsque l'utilisateur passe de l'UI de premier plan à l'écran d'accueil pendant que le travail en arrière-plan se poursuit.
1. Enregistrer une trace
Enregistrez une trace de 15 secondes qui capture les compteurs de mémoire du processus, la mémoire GPU et les modifications de oom_score_adj lors du démarrage d'une tâche en arrière-plan dans votre application et de l'accès à l'écran d'accueil :
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
size_kb: 32768
fill_policy: DISCARD
}
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 250
}
}
}
data_sources: {
config {
name: "android.gpu.memory"
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "oom/oom_score_adj_update"
ftrace_events: "gpu_mem/gpu_mem_total"
}
}
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace
2. Inspecter les pistes de processus dans l'UI de Perfetto
Ouvrez la trace dans l'interface utilisateur de Perfetto, puis développez les groupes de processus pour com.example.app et com.example.app:bg :
- Épinglez les pistes
oom_score_adjetmem.rss.anonpour les deux processus afin qu'elles s'affichent côte à côte en haut de la chronologie. - Recherchez le code temporel correspondant au moment où l'utilisateur appuie sur Accueil :
- Dans le processus de nœud de calcul,
oom_score_adjreste à200etmem.rss.anonreste stable et faible. - Dans le processus d'interface utilisateur,
oom_score_adjpasse de0à200, y reste pendant environ une seconde pendant l'arrêt deActivity, puis passe à700(PREV). Après le délai avant expiration de l'application précédente, il passe à la plage mise en cache, où le système peut compacter et récupérer sa mémoire sans affecter le nœud de calcul.
- Dans le processus de nœud de calcul,
Dans la figure 2, le processus d'interface utilisateur reste brièvement à 200 tandis que son Activity s'arrête, puis passe à 700, tandis que le nœud de calcul reste à 200 avec environ 19 Mo de RSS anonyme. À titre de comparaison, la figure 3 montre une trace de 35 secondes de la configuration colocalisée, où le processus unique reste à 50, puis à 200 avec toute sa mémoire d'UI.
Pour explorer ces vues, ouvrez la trace fractionnée et la trace colocalisée dans l'interface utilisateur de Perfetto.
3. Interroger l'espace mémoire utilisé élevé avec PerfettoSQL
Exécutez la requête PerfettoSQL suivante dans l'onglet Query (SQL) (Requête (SQL)) pour mesurer la taille moyenne de la mémoire RSS anonyme et de la mémoire RSS de fichier détenue par chaque processus de votre application à chaque niveau oom_score_adj :
INCLUDE PERFETTO MODULE android.memory.process;
SELECT
process_name,
score AS oom_score_adj,
ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;
Exemple de résultat pour la trace fractionnée de la figure 2 :
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.11 93.3 110.4
com.android.memorylab 200 1.06 126.8 110.5
com.android.memorylab 700 9.72 115.1 110.5
com.android.memorylab:remote 0 0.01 NULL NULL
com.android.memorylab:remote 200 12.87 18.9 53.7
Exemple de résultat pour la trace colocalisée de la figure 3 :
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.10 96.0 110.4
com.android.memorylab 50 16.04 117.6 110.5
com.android.memorylab 200 14.68 116.5 110.6
Dans la configuration fractionnée, seuls environ 19 Mo de mémoire anonyme restent à oom_score_adj = 200 après la première seconde. Dans la configuration colocalisée, environ 117 Mo restent dans 50, puis dans 200 tant que le service de premier plan est exécuté.
← Renvoyer les pages de mémoire à l'OS | ↑ Haut