ANRs (visualizações)

Conceitos e implementação do Jetpack Compose

Quando a linha de execução de interface de um app Android é bloqueada por muito tempo, o erro "O app não está respondendo" (ANR) é acionado. Se o app estiver em primeiro plano, o sistema vai mostrar uma caixa de diálogo ao usuário, como na figura 1. A caixa de diálogo ANR dá ao usuário a oportunidade de forçar o fechamento do app.

Caixa de diálogo ANR mostrada ao usuário.
Figura 1. Caixa de diálogo ANR mostrada ao usuário

ANRs são um problema porque fazem com que a linha de execução principal do app, que é responsável por atualizar a IU, não processe eventos de entrada do usuário ou não seja renderizada, causando frustração ao usuário. Para mais informações sobre a linha de execução principal do app, consulte Visão geral de processos e linhas de execução.

Um ANR é acionado para o app quando uma das seguintes condições ocorre:

  • Tempo de entrega de entradas esgotado: se o app não respondeu a um evento de entrada (como pressionamento de tecla ou toque na tela) em até cinco segundos.
  • Serviço executado: se um serviço declarado pelo app não consegue terminar a execução de Service.onCreate() e Service.onStartCommand()/Service.onBind() em alguns segundos.
  • **Service.startForeground() não chamado**: se o app usa Context.startForegroundService() para iniciar um novo serviço em primeiro plano mas o serviço não chama startForeground() em até cinco segundos.
  • Transmissão de intent: se uma BroadcastReceiver não termina de ser executada dentro de um período definido. Se o app tem alguma atividade em primeiro plano, esse tempo limite é de cinco segundos.
  • **JobScheduler interações**: se um JobService não retornar de JobService.onStartJob() ou JobService.onStopJob() em alguns segundos, ou se um job iniciado pelo usuário começar e seu app não chamar JobService.setNotification() alguns segundos depois de JobService.onStartJob() ser chamado. Em apps destinados ao Android 13 e versões anteriores, os ANRs são silenciosos e não são informados ao app. Para apps direcionados ao Android 14 e versões mais recentes, os ANRs são explícitos e informados ao app.

Se o app estiver apresentando ANRs, use as orientações desta página para diagnosticar e corrigir o problema.

Corrigir os problemas

Depois de identificar o problema, você pode usar as dicas desta seção para corrigir problemas encontrados com frequência.

Código lento na linha de execução principal

Identifique os lugares no código em que a linha de execução principal do app está ocupada por mais de cinco segundos. Procure os casos de uso suspeitos no app e tente reproduzir o ANR.

Por exemplo, a figura 2 mostra uma linha do tempo do Traceview em que a linha de execução principal está ocupada por mais de cinco segundos.

Figura 2. Linha do tempo do Traceview mostrando uma linha de execução
principal ocupada.

Figura 2. Linha do tempo do Traceview mostrando uma linha de execução principal movimentada.

A Figura 2 mostra que a maior parte do código com problemas acontece no onClick(View) gerenciador, conforme mostrado no exemplo de código abaixo.

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);
}

Nesse caso, você precisa mover o trabalho executado na linha de execução principal para uma linha de execução de worker. O framework do Android inclui classes que podem ajudar a mover a tarefa para uma linha de execução de worker. Consulte Linhas de execução de worker para mais informações.

E/S na linha de execução principal

A execução de operações de E/S na linha de execução principal é uma causa comum de operações lentas, o que pode causar ANRs. No Compose, os desenvolvedores costumam acionar acidentalmente leituras de disco (como SharedPreferences ou chamadas de banco de dados) ao tentar derivar o estado inicial.

Execute operações de E/S de longa duração fora da camada de interface. Use withContext(Dispatchers.IO) em uma ViewModel ou, melhor ainda, use um repositório na camada de dados. É recomendado mover todas as operações de E/S para uma linha de execução de worker, como mostrado na seção anterior.

Alguns exemplos de operações de E/S são operações de rede e armazenamento. Para ver mais informações, consulte Como realizar operações de rede e Como salvar dados.

Contenção de bloqueio

Em alguns casos, o trabalho que causa o ANR não é executado diretamente na linha de execução principal do app. Se uma linha de execução de worker tiver um bloqueio em um recurso necessário para a linha de execução principal concluir o trabalho dela, um ANR pode acontecer.

Por exemplo, a figura 3 mostra uma linha do tempo do Traceview em que a maior parte do trabalho é realizada em uma linha de execução de worker.

Figura 3. Linha do tempo do Traceview que mostra o trabalho que está sendo executado em uma linha de execução
de worker.

Figura 3. Linha do tempo do Traceview que mostra o trabalho que está sendo executado em uma linha de execução de worker.

No entanto, se os usuários ainda estiverem recebendo ANRs, analise o status da linha de execução principal no Android Device Monitor. Normalmente, a linha de execução principal fica no RUNNABLE estado quando está pronta para atualizar a IU e, em geral, é responsiva.

Mas se a linha de execução principal não retoma a execução, ela fica no BLOCKED estado e não pode responder aos eventos. O estado é mostrado no Android Device Monitor como Monitor ou Wait, conforme mostrado na Figura 5.

Figura 4. Linha de execução principal no
status "Monitor".

Figura 4. Linha de execução principal no status "Monitor".

O rastro abaixo mostra a linha de execução principal de um app que está bloqueada aguardando um 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)
...

Analisar o rastro pode ajudar você a localizar o código que bloqueia a linha de execução principal. O código abaixo é responsável por manter o bloqueio da linha de execução principal no rastro 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]);
      }
  }
}

Outro exemplo é a linha de execução principal de um app que está aguardando um resultado de uma linha de execução de worker, conforme mostrado no código abaixo. O uso de wait() e notify() não é um padrão recomendado em Kotlin, que tem mecanismos para processar a simultaneidade. Ao programar com Kotlin, use mecanismos específicos dessa linguagem, se possível.

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();
      }
  }
}

Há algumas outras situações que podem bloquear a linha de execução principal, incluindo aquelas que usam Lock, Semaphore, bem como um pool de recursos (como um pool de conexões de banco de dados) ou outros mecanismos de exclusão mútua (mutex) mecanismos.

Avalie os bloqueios que o app mantém nos recursos em geral, mas se quiser evitar ANRs, analise os bloqueios dos recursos exigidos pela linha de execução principal.

Confira se os bloqueios são mantidos pelo menor tempo possível ou, melhor ainda, verifique se o app precisa mesmo dessa retenção. Se você estiver usando o bloqueio para determinar quando atualizar a IU com base no processamento de uma linha de execução de worker, use mecanismos como onProgressUpdate() e onPostExecute() para comunicação entre o worker e as principais linhas de execução.

Broadcast receivers lentos

Os apps podem responder a mensagens de transmissão, como ativar ou desativar o modo avião ou mudar o status de conectividade usando broadcast receivers. Um ANR ocorre quando um app demora muito para processar a mensagem de transmissão.

Um ANR ocorre nestes casos:

  • Após um período considerável, um broadcast receiver ainda não concluiu a execução do método onReceive.
  • Um broadcast receiver chama goAsync e não consegue chamar finish no objeto PendingResult.

O app só pode executar operações curtas no onReceive método de um BroadcastReceiver. No entanto, se o app exigir processamento mais complexo como resultado de uma mensagem de transmissão, você vai precisar adiar a tarefa para uma ViewModel (aproveitando o poder das corrotinas, escopos e distribuidores do Kotlin) se a tarefa levar alguns segundos no máximo, qualquer tipo de detentor de estado, ou para o WorkManager para tarefas que devem levar mais de alguns segundos.

Você pode usar ferramentas como o Traceview para identificar se o broadcast receiver executa operações de longa duração na linha de execução principal do app. Por exemplo, a figura 6 mostra a linha do tempo de um broadcast receiver que processa uma mensagem na linha de execução principal por aproximadamente 100 segundos.

Figura 5. Linha do tempo do Traceview mostrando o trabalho de BroadcastReceiver na
linha de execução principal

Figura 5. Linha do tempo do Traceview mostrando o trabalho de BroadcastReceiver na linha de execução principal.

Esse comportamento pode ser causado pela execução de operações de longa duração no onReceive() método do BroadcastReceiver, conforme mostrado no exemplo abaixo.

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);
}

Em situações como essas, é recomendado mover a operação de longa duração para um IntentService porque ela usa uma linha de execução de worker para executar o trabalho. O código abaixo mostra como usar um IntentService para processar uma operação de longa duração:

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);
  }
}

Como resultado do uso do IntentService, a operação de longa execução é executada em uma linha de execução de worker, em vez da linha de execução principal. A Figura 7 mostra o trabalho adiado para a linha de execução de worker na linha do tempo do Traceview.

Figura 6. Linha do tempo do Traceview que mostra a mensagem de transmissão processada em uma
linha de execução de worker.

Figura 6. Linha do tempo do Traceview que mostra a mensagem de transmissão processada em uma linha de execução de worker.

O broadcast receiver pode usar goAsync() para sinalizar ao sistema que ele precisa de mais tempo para processar a mensagem. No entanto, você precisa chamar finish() no PendingResult objeto. O exemplo a seguir mostra como chamar finish() para permitir que o sistema recicle o broadcast receiver e evite um 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);

No entanto, mover o código de um broadcast receiver lento para outra linha de execução e usar goAsync() não corrige o ANR se a transmissão está em segundo plano. O tempo limite do ANR ainda vai ser válido.