Muchas apps realizan tareas legítimas cuando su IU ya no está visible, como descargar paquetes de recursos grandes, sincronizar bases de datos locales, transmitir audio, grabar entrenamientos de ejercicio o mantener una sesión de dispositivo conectado. Cuando
este trabajo en segundo plano se ejecuta dentro del mismo proceso del SO que la IU en primer plano y
el motor de gráficos, todo el proceso se mantiene en un nivel de importancia elevado
(oom_score_adj entre 0 y 250) junto con toda la memoria que
asignó la IU.
En esta página, se explica por qué esto perjudica a tu app y al resto del dispositivo, cómo mover el trabajo en segundo plano a un proceso liviano dedicado con android:process y cómo verificar el ahorro de memoria con adb shell y Perfetto.
Por qué el trabajo de un solo proceso conserva la memoria de la IU
Android administra la memoria y la finalización de procesos con la granularidad de un proceso del SO (pid), no de componentes individuales (Activity, Service o BroadcastReceiver). El Administrador de actividades (OomAdjuster) asigna a cada proceso un solo valor de oom_score_adj que refleja el componente activo o la vinculación entrante más importante de ese proceso:
- Cuando el usuario interactúa con tu
Activity, el proceso se ejecuta en el estadoTOP(oom_score_adj = 0). - Cuando el usuario sale de la app y esta no tiene componentes activos en segundo plano, el proceso se mueve al bucket
PREV(oom_score_adj = 700) oCACHED(oom_score_adj >= 900). En el bucket almacenado en caché, la plataforma congela el proceso con el congelador de cgroup v2, compacta su memoria conCachedAppOptimizery permite quelmkdfinalice el proceso primero bajo presión de memoria. - Sin embargo, si el mismo proceso también ejecuta un servicio en primer plano (como los tipos
dataSync,mediaPlayback,connectedDeviceolocation, enoom_score_adj = 200) o aloja un servicio al que se vincula el sistema o algún otro cliente importante, todo el proceso permanece en esa prioridad elevada incluso después de que se detiene elActivity.
Cuando un proceso de IU pesado permanece en oom_score_adj = 200, varias categorías grandes de memoria permanecen residentes:
Activitydetenida y jerarquía de vistas: A menos que laActivityfinalice de forma explícita, suWindow, el árbol de vistas, las estructuras de diseño y el estado de la vista permanecen accesibles en el montón de Java mientras el usuario está en otra app.- Contextos de gráficos nativos y asignaciones de GPU: El
HardwareRendererestándar conserva su contexto de GPU y sus cachés, mientras que unActivitydetenido reside en un proceso que no se almacena en caché. Las apps y los motores de juegos que crean sus propios contextos de Vulkan (VkDevice,VkSwapchainKHR, grupos de descriptores, cachés de canalización) o de OpenGL ES (EGLContext, texturas, búferes de vértices) también conservan sus estructuras de controladores, programas de sombreadores y búferes de gráficos, a menos que el motor desactive de forma explícita el dispositivo de gráficos cuando se detenga. - Fragmentación del montón y sobrecarga del entorno de ejecución: Incluso cuando una app libera cachés en
onTrimMemory, las páginas del asignador nativo parcialmente propagadas, las páginas de copia en escritura de Zygote sucias, las bibliotecas compartidas cargadas (.so) y los metadatos del entorno de ejecución permanecen residentes en la memoria privada del proceso. Debido a que el proceso está por encima del umbral de almacenamiento en caché (oom_score_adj < 900), la plataforma no lo congela ni ejecuta la compactación de apps almacenadas en caché en él. Para obtener más información sobre este efecto con los servicios vinculados, consulta Coloca un servicio vinculado con una IU que use mucha memoria.
Cómo un proceso en segundo plano optimizado protege tu app
Mantener la memoria de tamaño de primer plano en un estado de segundo plano pone en riesgo tu propio trabajo en segundo plano:
- Los límites de memoria en segundo plano son más estrictos que los límites en primer plano. En Android 17 y versiones posteriores, el limitador de memoria aplica un límite de memoria no visible más pequeño a tu app cuando ninguno de sus procesos muestra una IU visible, incluso mientras se ejecuta un servicio en primer plano. El límite abarca todos los procesos de tu app en conjunto. Si el proceso que ejecuta tu servicio en primer plano aún conserva su memoria de IU y memoria de gráficos, esa memoria se tiene en cuenta para el límite más pequeño. Cuando tu app supera el límite, el kernel la limita mientras recupera sus páginas, lo que ralentiza la descarga, la sincronización o la reproducción que se está ejecutando. Si tu app sigue asignando memoria después de que se agota el espacio de intercambio, el sistema la finaliza. Para verificar si tu app se vio afectada, consulta Límites de memoria de la app.
- Los procesos más grandes se finalizan primero cuando hay presión de memoria. Cuando
lmkddebe liberar memoria y está configurado para elegir el proceso más pesado en un bucket de importancia (ro.lmk.kill_heaviest_task), se finaliza un proceso de servicio en primer plano que también contiene cientos de MB de memoria de IU antes de procesos más pequeños con la misma importancia, y tu trabajo en segundo plano se detiene con él. Consulta Interacción entre kswapd y lmkd. - El estado de la IU almacenado en caché se puede congelar, compactar y volver a escribir en la memoria flash. Permitir que el estado de la IU en primer plano se mueva a un proceso almacenado en caché permite que el SO congele el proceso, compacte su memoria en zRAM y vuelva a escribir sus páginas inactivas en el almacenamiento flash. Su espacio en memoria residente se reduce mientras el estado de la IU permanece intacto, lo que brinda al trabajo en segundo plano y a la IU almacenada en caché la mejor oportunidad de sobrevivir hasta que el usuario regrese.
- Los presupuestos de memoria por proceso se vuelven prácticos. Un proceso que solo ejecuta trabajo en segundo plano tiene un conjunto de trabajo pequeño y predecible, por lo que puedes asignarle un presupuesto de memoria ajustado sin restringir la IU.
Ejecutar un segundo proceso puede parecer que aumenta el espacio en memoria total de tu app, ya que un segundo entorno de ejecución tiene un costo de referencia pequeño. Esa comparación solo es válida si un solo proceso pudiera liberar por completo todo su estado de IU y gráficos cada vez que pasa al trabajo en segundo plano, lo que las apps y los kits de herramientas de IU no pueden hacer de manera confiable en la práctica. Permitir que el SO congele, compacte y reescriba un proceso de IU almacenado en caché independiente usa mucha menos memoria física en el caso común, mantiene tu app dentro del límite no visible y reduce las finalizaciones por memoria insuficiente para tu app y en todo el dispositivo.
Aísla el trabajo en segundo plano en un proceso dedicado
Para eliminar la residencia de la IU y los gráficos durante el trabajo en segundo plano, ejecuta componentes en segundo plano de larga duración en un proceso ligero dedicado mientras mantienes la IU y el motor de gráficos en el proceso principal:
| Proceso | Declaración del manifiesto | Componentes alojados | Estado cuando el usuario sale de la IU durante el trabajo en segundo plano |
|---|---|---|---|
| Proceso de IU principal | Predeterminado (com.example.app) |
Activity, vistas de IU, cachés de imágenes, renderizador de Vulkan u OpenGL ES, motor de audio y escenas
|
Disminuye a PREV y, luego, a CACHED (oom_score_adj >= 900). Se congela, se compacta y se vuelve a escribir en la memoria flash.
|
| Proceso de trabajador en segundo plano | android:process=":bg" |
Service en primer plano, descargador de recursos en segundo plano, trabajador de sincronización o administrador de dispositivos conectados
|
Se ejecuta en FGS (oom_score_adj = 200) solo con el montón y el código del trabajador, y sin vistas de IU ni contextos de GPU.
|
Declara los presupuestos de procesos y memoria
Asigna tu Service en segundo plano a un subproceso privado configurando
android:process=":bg" en AndroidManifest.xml. Los dos puntos iniciales (:)
crean un proceso privado con alcance para el UID de tu app.
En Android 17 QPR2 (nivel de API 37.2) y versiones posteriores, combina la división de procesos con presupuestos de memoria de la app por proceso dentro del bloque <processes> para que el sistema operativo aplique un límite estricto de conjunto de trabajo en el trabajador en segundo plano sin restringir el proceso de la IU en primer plano:
<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>
El uso de memoria en :bg se descuenta de su propio presupuesto y del presupuesto de toda la app. En los dispositivos que ejecutan versiones anteriores a Android 17 QPR2, el sistema ignora <memory-budget>, mientras que android:process=":bg" funciona en todos los niveles de API.
Inicialización de Gate en Application.onCreate
Android crea una instancia de tu clase Application y llama a Application.onCreate en cada proceso que se genera para tu paquete. Un error común cuando se adopta android:process=":bg" es dejar kits de herramientas de IU pesados, grupos de carga de imágenes, SDKs de Analytics o inicialización de motores nativos (System.loadLibrary) dentro de un método Application.onCreate sin protección. Si lo haces, se inflará el proceso :bg con la misma huella de referencia que intentas evitar.
Verifica Application.getProcessName, que está disponible en Android 9 (nivel de API 28) y versiones posteriores, dentro de onCreate, y, luego, inicializa solo las dependencias que requiere el proceso actual:
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");
}
}
Las bibliotecas que se inicializan a través de un proveedor de contenido, como Jetpack App Startup, solo se inicializan en el proceso principal, ya que un proveedor de contenido declarado sin android:process se ejecuta en el proceso principal. Si el código de :bg depende de una biblioteca de ese tipo, inicialízala de forma explícita en initBackgroundWorkerProcess.
Si compilas con un motor de juego, como Unity, implementa el servicio en segundo plano en una biblioteca o un complemento de Android simple escrito en Java o Kotlin que no haga referencia a clases de motor, como UnityPlayer. Luego, el proceso :bg nunca
carga las bibliotecas nativas del motor. Para confirmar, verifica que
adb shell cat /proc/<pid>/maps para el proceso :bg no incluya
libunity.so ni libil2cpp.so.
Cómo comunicarse sin fijar el proceso de la IU
Cuando el usuario tiene MainActivity abierto mientras AssetDownloadService se ejecuta en :bg, la IU suele necesitar mostrar el progreso de la descarga en vivo. La importancia del proceso fluye de un cliente al proceso que aloja el servicio o el proveedor de contenido que usa, por lo que la dirección de cada dependencia es importante:
- La vinculación de la IU al trabajador es segura. Cuando
MainActivityse vincula aAssetDownloadService, el sistema eleva:bga la importancia de la IU visible. Cuando el usuario sale, el proceso de la IU se almacena en caché y la vinculación ya no eleva nada. - El trabajador que depende del proceso de la IU no es seguro. Si el código de
:bgse vincula a un servicio o consulta un proveedor de contenido que se ejecuta en el proceso principal, el sistema eleva el proceso principal a la importancia elevada del trabajador, y la memoria de la IU vuelve a fijarse. En su lugar, haz que la IU registre una devolución de llamada con el trabajador, como unaMessengero una interfaz de devolución de llamada de AIDL. Pasar un objeto Binder como devolución de llamada no cambia la importancia de ninguno de los procesos.
Cancela el registro de la devolución de llamada antes de desvincularla para que el trabajador nunca llame al proceso de la IU después de que el sistema lo congele. En el siguiente ejemplo, se vincula al trabajador solo mientras la IU 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);
}
}
Para obtener más información sobre cómo las marcas de vinculación afectan a oom_score_adj en los procesos, consulta Vinculaciones de servicios y estados de procesos.
Coordina el estado compartido en el disco de forma segura
Debido a que el proceso principal y :bg se ejecutan en espacios de direcciones independientes con entornos de ejecución y descriptores de archivos separados, se aplica lo siguiente:
- SQLite y Room: Usa el almacenamiento de registros de escritura anticipada (el modo de registro predeterminado en Room en la mayoría de los dispositivos) para que ambos procesos puedan leer y escribir en la base de datos de forma simultánea. No llames a
enableMultiInstanceInvalidationdesde:bg. Room aloja su servicio de invalidación en el proceso principal, por lo que el trabajador se vincularía al proceso principal y mantendría la memoria de la IU en la importancia elevada del trabajador. En cambio, haz que el trabajador informe los cambios a través de la devolución de llamada que registra la IU y que esta vuelva a consultar la base de datos cuando se inicie. - Preferencias: No compartas
SharedPreferencesentre procesos. Usa DataStore en código de varios procesos o pasa el estado de forma explícita a través de IPC. - Archivos de recursos: Escribe los paquetes de recursos descargados en un archivo de etapa de pruebas en
:bg, llama afdatasyncy, luego, aposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)en:bgdespués de escribir (como se muestra en Cómo devolver páginas de memoria al SO) y cambia el nombre del archivo de forma atómica. Permite que la IU reciba el resultado cuando se inicie y se vincule al trabajador, en lugar de llamar a un proceso de IU que podría estar inmovilizado.
Observa el efecto en adb shell
Para verificar que la división de los procesos en primer y segundo plano aísle el uso elevado de memoria, inspecciona los estados de los procesos de Activity Manager y dumpsys meminfo después de navegar a la pantalla principal. El resultado de ejemplo de esta sección proviene de la app de ejemplo de MemoryLab, que ejecuta la misma asignación nativa de 64 MB en su proceso de IU en ambas configuraciones y aloja su servicio en primer plano dividido en un proceso :remote.
1. Compara los estados de los procesos y las puntuaciones de OOM
Inicia la carga de trabajo en segundo plano, presiona el botón de inicio para que se detenga el Activity y consulta Activity Manager:
adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"
En una arquitectura de un solo proceso, el proceso principal permanece elevado, aunque se detenga su Activity. Durante unos 15 segundos después de que el usuario sale, Activity Manager mantiene un proceso que ejecuta un servicio en primer plano en oom_score_adj = 50 (fg-service-act) y, luego, lo reduce a 200 (fg-service). El siguiente resultado se capturó 20 segundos después de presionar Inicio:
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
En una arquitectura de proceso dividido, solo el proceso de trabajador permanece en oom_score_adj = 200, mientras que el proceso de IU principal se reduce a PREV (oom_score_adj = 700) y, luego, al bucket almacenado en caché (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
Una vez que vence el tiempo de espera de la app anterior, el proceso principal se mueve al bucket almacenado en caché (cch, oom_score_adj = 900, previous-expired) y pierde todas sus capacidades de proceso (---------), incluido el tiempo de CPU implícito (I). En ese momento, el sistema lo congela y puede compactarlo o reclamarlo en cualquier momento sin interrumpir el proceso de trabajo.
2. Compara dumpsys meminfo entre procesos
Compara el resumen de dumpsys meminfo del trabajador con privilegios elevados con el proceso de la IU:
adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app
Resultado de muestra de App Summary para el proceso de trabajador de MemoryLab en
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
Resultado de muestra de App Summary para el proceso de la IU de MemoryLab en oom_score_adj = 700, que contiene la asignación nativa de 64 MB en 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
El proceso de trabajador necesita menos de 5 MB de PSS, ya que comparte la mayor parte de su código y páginas de tiempo de ejecución con Zygote. En una app que se renderiza con Vulkan o OpenGL ES, la fila Graphics del proceso de IU también incluye la memoria de la GPU.
Esta muestra se capturó en un dispositivo virtual Cuttlefish con renderización de software, por lo que Graphics es 0 en ambos procesos.
3. Inspecciona la contabilidad de cgroup por proceso
Cada proceso en tu UID tiene su propio directorio de cgroup v2 dentro de un cgroup superior para tu app. El siguiente comando enumera oom_score_adj, memory.current y el memory.high de toda la app para cada proceso de tu app:
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
'
Resultado de muestra de MemoryLab después de presionar Inicio:
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
Al proceso de trabajador se le cobran menos de 4 MB, mientras que al proceso de IU se le cobran alrededor de 98 MB. Cada proceso tiene su propio memory.current, pero el
limitador de memoria establece su límite en el cgroup superior que contiene todos los procesos
de tu app. Ambas líneas muestran el mismo app.memory.high de 966,787,072 bytes,
que es el límite no visible de 922 MB en el siguiente resultado.
Para ver los límites visibles y no visibles que el Limitador de memoria aplica en el dispositivo, ejecuta el siguiente comando:
adb shell am memory-limiter status
A continuación, se muestra un resultado de ejemplo de un dispositivo con 4 GB de RAM, en el que el límite no visible es la mitad del límite 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
Observa el efecto en Perfetto
Perfetto te permite visualizar el cronograma de oom_score_adj y RSS en ambos procesos a medida que el usuario pasa de la IU en primer plano a la pantalla principal mientras continúa el trabajo en segundo plano.
1. Cómo registrar un seguimiento
Graba un registro de 15 segundos que capture los contadores de memoria de procesos, la memoria de la GPU y los cambios de oom_score_adj mientras inicias un trabajo en segundo plano en tu app y navegas a la pantalla principal:
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. Inspecciona las pistas de procesos en la IU de Perfetto
Abre el registro en la IU de Perfetto y expande los grupos de procesos para com.example.app y com.example.app:bg:
- Fija las pistas
oom_score_adjymem.rss.anonpara ambos procesos, de modo que aparezcan una al lado de la otra en la parte superior de la línea de tiempo. - Busca la marca de tiempo en la que el usuario presiona Inicio:
- En el proceso de trabajador,
oom_score_adjse mantiene en200ymem.rss.anonse mantiene plano y pequeño. - En el proceso de la IU,
oom_score_adjaumenta de0a200, permanece allí durante aproximadamente un segundo mientras se detieneActivityy, luego, aumenta a700(PREV). Después del tiempo de espera de la app anterior, se mueve al rango almacenado en caché, donde el sistema puede compactar y recuperar su memoria sin afectar al trabajador.
- En el proceso de trabajador,
En la Figura 2, el proceso de IU permanece brevemente en 200 mientras se detiene su Activity y, luego, aumenta a 700, mientras que el trabajador permanece en 200 con alrededor de 19 MB de RSS anónimo. A modo de comparación, en la figura 3, se muestra un seguimiento de 35 segundos de la configuración de ubicación conjunta, en la que el proceso único permanece en 50 y, luego, en 200 con toda su memoria de IU.
Para explorar estas vistas, abre el seguimiento dividido y el seguimiento ubicado en el mismo lugar en la IU de Perfetto.
3. Consulta el espacio en memoria elevado con PerfettoSQL
Ejecuta la siguiente consulta de PerfettoSQL en la pestaña Query (SQL) para medir el RSS anónimo y de archivo promedio que mantiene cada proceso de tu app en cada nivel de 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;
Resultado de muestra para el seguimiento dividido en la Figura 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
Resultado de muestra para el seguimiento ubicado en el mismo lugar de la Figura 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
En la configuración dividida, solo alrededor de 19 MB de memoria anónima permanecen en oom_score_adj = 200 después del primer segundo. En la configuración de ubicación conjunta,
alrededor de 117 MB permanecen en 50 y, luego, en 200 mientras se ejecuta el servicio
en primer plano.
← Devuelve páginas de memoria al SO | ↑ Arriba