Conceptos y la implementación de Jetpack Compose
Cuando se bloquea durante demasiado tiempo el subproceso de IU de una app para Android, se activa un error del tipo "Aplicación no responde" (ANR). Si la app está en primer plano, el usuario podrá ver un diálogo del sistema, como se observa en la Figura 1. Este diálogo de error de ANR le permite forzar el cierre de la app.
Los errores de ANR representan un problema porque el subproceso principal de la app, que se encarga de actualizar la IU, no puede procesar eventos de entrada del usuario ni obtener datos, lo que le genera frustración al usuario. Para obtener más información sobre el subproceso principal de la app, consulta Descripción general de procesos y subprocesos.
Cuando se produce una de las siguientes condiciones, se activa un error de ANR en tu app:
- Tiempo de espera para el ingreso de datos agotado: La app no respondió a un evento de entrada (como presionar una tecla o tocar la pantalla) en 5 segundos
- Servicio en ejecución: La app declaró un servicio que no puede terminar de
ejecutar
Service.onCreate()yService.onStartCommand()/Service.onBind()en unos segundos. **Service.startForeground()no se llama**: La app usaContext.startForegroundService()para iniciar un servicio nuevo en primer plano pero el servicio no llama astartForeground()en 5 segundos.- Transmisión del intent: Cuando un objeto
BroadcastReceiverno terminó de ejecutarse dentro de un período establecido. Si la app tiene alguna actividad en primer plano, el tiempo de espera es de 5 segundos. **JobSchedulerinteracciones**: Si unJobServiceno devuelve un valor deJobService.onStartJob()oJobService.onStopJob()en unos segundos, o si se inicia un trabajo iniciado por el usuario y tu app no llama aJobService.setNotification()unos segundos después de que se llama aJobService.onStartJob(). En el caso de las apps orientadas a Android 13 y versiones anteriores, los errores de ANR se silencian y no se informan a la app. En el caso de las apps orientadas a Android 14 y versiones posteriores, estos errores son explícitos y se informan a la app.
Si tu app presenta errores de ANR, puedes seguir las indicaciones que se incluyen en este artículo para diagnosticar el problema y corregirlo.
Cómo corregir problemas
Una vez que hayas identificado el problema, puedes usar las sugerencias incluidas en esta sección para corregir problemas habituales.
Código lento en el subproceso principal
Identifica las partes de tu código donde el subproceso principal de la app está ocupado durante más de 5 segundos. Busca los casos de uso sospechosos en tu app e intenta reproducir el error de ANR.
Por ejemplo, en la figura 2, se muestra un cronograma de Traceview donde el subproceso principal está ocupado durante más de 5 segundos.

Figura 2. Cronograma de Traceview que muestra un subproceso principal ocupado
En la Figura 2, se muestra que la mayor parte del código incorrecto ocurre en el
onClick(View) controlador, como se observa en el siguiente ejemplo de código:
Kotlin
override fun onClick(v: View) {
// This task runs on the main thread.
BubbleSort.sort(data)
}
Java
@Override
public void onClick(View view) {
// This task runs on the main thread.
BubbleSort.sort(data);
}
En este caso, debes pasar el trabajo que se ejecuta en el subproceso principal a un subproceso de trabajo. El framework de Android incluye clases que pueden ayudarte a mover la tarea a un subproceso de trabajo. Consulta Subprocesos de trabajo para obtener más información.
E/S en el subproceso principal
Cuando se ejecutan operaciones de E/S en el subproceso principal, las operaciones se ralentizan y se pueden producir errores de ANR. En Compose, los desarrolladores suelen activar accidentalmente lecturas de disco (como SharedPreferences o llamadas a bases de datos) mientras intentan obtener el estado inicial.
Ejecuta operaciones de E/S de larga duración fuera de la capa de IU. Usa
withContext(Dispatchers.IO) en un ViewModel o, mejor aún, usa un repositorio
en la capa de datos. Se recomienda pasar todas las operaciones de E/S a un subproceso de trabajador, como se muestra en la sección anterior.
Las operaciones de red y almacenamiento son algunos ejemplos de operaciones de E/S. Para obtener más información, consulta Cómo llevar a cabo operaciones de red y Cómo guardar datos.
Contención de bloqueo
En algunas situaciones, el trabajo que ocasiona el error de ANR no se ejecuta de forma directa en el subproceso principal de la app. Si un subproceso de trabajo bloquea un recurso que el subproceso principal requiere para completar su trabajo, puede producirse un error de ANR.
Por ejemplo, en la Figura 3, se muestra un cronograma de Traceview en el que la mayor parte del trabajo se realiza en un subproceso de trabajo.

Figura 3. Cronograma de Traceview que muestra el trabajo que se ejecuta en un subproceso de trabajo
Sin embargo, si los usuarios siguen teniendo errores de ANR, debes analizar el estado del subproceso principal en Android Device Monitor. Por lo general, si el subproceso principal está listo para actualizar la IU y tiene capacidad de respuesta, aparecerá con el estado
RUNNABLE.
Sin embargo, si no puede reanudar la ejecución, aparecerá con el BLOCKED
estado y no podrá responder a los eventos. En Android Device Monitor, el estado aparecerá como
Monitor o Wait, como se muestra en la Figura 5.

Figura 4. Subproceso principal en el estado Monitor
En el siguiente registro, se muestra el subproceso principal de una app que está bloqueado a la espera de un recurso:
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
Al revisar el registro, podrás encontrar el código que bloquea el subproceso principal. El siguiente código contiene el bloqueo que impide el subproceso principal en el registro anterior:
Kotlin
override fun onClick(v: View) {
// The worker thread holds a lock on lockedResource
LockTask().execute(data)
synchronized(lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
class LockTask : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? =
synchronized(lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0])
}
}
Java
@Override
public void onClick(View v) {
// The worker thread holds a lock on lockedResource
new LockTask().execute(data);
synchronized (lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0]);
}
}
}
Otro ejemplo es el subproceso principal de una app que espera un resultado de un subproceso de trabajo, como se muestra en el siguiente código. Ten en cuenta que no se recomienda usar los patrones wait() y notify() en Kotlin, que tiene sus propios mecanismos para procesar la simultaneidad. Al usar Kotlin, debes emplear mecanismos específicos de Kotlin cuando sea posible.
Kotlin
fun onClick(v: View) {
val lock = java.lang.Object()
val waitTask = WaitTask(lock)
synchronized(lock) {
try {
waitTask.execute(data)
// Wait for this worker thread's notification
lock.wait()
} catch (e: InterruptedException) {
}
}
}
internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
synchronized(lock) {
BubbleSort.sort(params[0])
// Finished, notify the main thread
lock.notify()
}
}
}
Java
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// Wait for this worker thread’s notification
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// Finished, notify the main thread
notify();
}
}
}
Existen otras situaciones que pueden bloquear el subproceso principal, incluidos
los subprocesos que usan Lock, Semaphore, así como un grupo de recursos
(como un grupo de conexiones de bases de datos) u otros mecanismos de exclusión mutua (mutex)
Debes evaluar los bloqueos que tiene tu app en los recursos generales. Sin embargo, si deseas evitar los errores de ANR, debes analizar los bloqueos retenidos para los recursos que requiere el subproceso principal.
Asegúrate de que los bloqueos se mantengan durante el menor tiempo posible o, mejor aún, evalúa si la app debe retenerlos en primer lugar. Si usas el
bloqueo para determinar cuándo actualizar la IU según el procesamiento de un subproceso de trabajo,
emplea mecanismos como onProgressUpdate() y onPostExecute() para
establecer la comunicación entre el subproceso principal y el de trabajo.
Receptores de emisión lenta
A través de los receptores de emisión, las apps pueden responder a los mensajes de emisión, como habilitar o inhabilitar el modo de avión o un cambio en el estado de conectividad. Los errores de ANR se producen cuando una app tarda demasiado en procesar el mensaje de emisión.
Los errores de ANR se producen en los siguientes casos:
- Cuando un receptor de transmisiones no terminó de ejecutar su
onReceivemétodo en un lapso considerable. - Cuando un receptor de transmisiones llama a
goAsyncy no puede llamar afinishen el objetoPendingResult.
Tu app solo debe realizar operaciones cortas en el onReceive método de
un BroadcastReceiver. Sin embargo, si su aplicación requiere un procesamiento más complejo
como resultado de un mensaje de emisión, debe diferir la tarea a un
ViewModel (aprovechando el poder de las corrutinas, los alcances y los distribuidores de Kotlin)
si se espera que la tarea tarde unos segundos como máximo, cualquier tipo de titular de estado,
o a WorkManager para las tareas que se espera que tarden más de unos segundos.
Puedes usar herramientas como Traceview para identificar si tu receptor de transmisiones ejecuta operaciones de larga duración en el subproceso principal de la app. Por ejemplo, en la figura 6, se muestra el cronograma de un receptor de transmisiones que procesa un mensaje en el subproceso principal durante aproximadamente 100 segundos.

Figura 5. Cronograma de Traceview que muestra el trabajo de BroadcastReceiver en el subproceso principal
Este comportamiento puede deberse a la ejecución de operaciones de larga duración en el
onReceive() método de BroadcastReceiver, como se observa en el
siguiente ejemplo:
Kotlin
override fun onReceive(context: Context, intent: Intent) {
// This is a long-running operation
BubbleSort.sort(data)
}
Java
@Override
public void onReceive(Context context, Intent intent) {
// This is a long-running operation
BubbleSort.sort(data);
}
En estas situaciones, se recomienda pasar la operación de larga duración a
un IntentService, ya que utiliza un subproceso de trabajador para ejecutar su trabajo.
En el siguiente código, se muestra cómo usar un IntentService para procesar una
operación de larga duración:
Kotlin
override fun onReceive(context: Context, intent: Intent) {
Intent(context, MyIntentService::class.java).also { intentService ->
// The task now runs on a worker thread.
context.startService(intentService)
}
}
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
BubbleSort.sort(data)
}
}
Java
@Override
public void onReceive(Context context, Intent intent) {
// The task now runs on a worker thread.
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
Al usar IntentService, se ejecuta la operación de larga duración
en un subproceso de trabajador, en lugar del subproceso principal. En la Figura 7, se muestra el trabajo diferido al subproceso de trabajo en el cronograma de Traceview.

Figura 6. Cronograma de Traceview que muestra el mensaje de emisión procesado en un subproceso de trabajo
Tu receptor de transmisiones puede usar goAsync() para indicarle al sistema que necesita más tiempo para procesar el mensaje. Sin embargo, debes llamar a
finish() en el PendingResult objeto. En el siguiente ejemplo, se muestra cómo llamar a finish() para que el sistema recicle el receptor de transmisiones a fin de evitar un error de ANR:
Kotlin
val pendingResult = goAsync()
object : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
// This is a long-running operation
BubbleSort.sort(params[0])
pendingResult.finish()
return 0L
}
}.execute(data)
Java
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// This is a long-running operation
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
Sin embargo, mover el código de un receptor de transmisiones lenta a otro subproceso y
usar goAsync() no corregirá el ANR si la transmisión está en segundo plano.
Aún se aplica el tiempo de espera del error de ANR.