ANRs

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

Figura 1. Caixa de diálogo ANR exibida para o usuário.

Figura 1. Caixa de diálogo ANR exibida para o 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 ou desenhe, causando frustração ao usuário. Para mais informações sobre a linha de execução principal do app, consulte Processos e linhas de execução.

Um ANR será acionado para seu app quando uma das seguintes condições ocorrer:

  • Enquanto sua atividade estava em primeiro plano, seu app não respondeu a um evento de entrada ou BroadcastReceiver, como eventos de pressionamento de tecla ou toque na tela, em um intervalo de 5 segundos.
  • Enquanto não havia uma atividade em primeiro plano, o BroadcastReceiver não terminou a execução em um tempo considerável.

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

Detectar e diagnosticar problemas

O Android oferece vários meios de informar que seu app está com um problema e ajudar a diagnosticá-lo. Se você já tiver publicado o app, o "Android vitals" poderá alertar que o problema está ocorrendo, e há ferramentas de diagnóstico para ajudar você a encontrar o problema.

Android vitals

O recurso "Android vitals" pode ajudar a melhorar o desempenho do seu app ao alertar pelo Play Console quando o app apresentar excesso de falhas. O "Android vitals" considera que há erros ANR quando um app:

  • exibe pelo menos um ANR em pelo menos 0,47% de suas sessões diárias;
  • exibe dois ou mais ANRs em pelo menos 0,24% de suas sessões diárias.

Uma sessão diária refere-se a um dia em que seu app foi usado.

Para ver informações sobre como o Google Play coleta dados do "Android vitals", consulte a documentação do Play Console.

Como diagnosticar ANRs

Há alguns padrões comuns que precisam ser observados ao diagnosticar ANRs:

  1. O app está fazendo operações lentas envolvendo E/S na linha de execução principal.
  2. O app está fazendo um cálculo longo na linha de execução principal.
  3. A linha de execução principal está fazendo uma chamada de vinculação síncrona para outro processo e esse outro processo está demorando muito para retornar.
  4. A linha de execução principal está bloqueada à espera de um bloco sincronizado para uma operação longa que está acontecendo em outra linha de execução.
  5. A linha de execução principal está em um impasse com outra linha, seja em seu processo ou em uma chamada de vinculação. A linha de execução principal não está apenas aguardando uma operação longa terminar, mas está em uma situação de impasse. Para mais informações, consulte Impasse na Wikipédia.

As técnicas a seguir podem ajudar a descobrir qual dessas causas está causando seus ANRs.

Modo restrito

Usar o StrictMode ajuda a encontrar operações de E/S acidentais na linha de execução principal enquanto você desenvolve seu app. Você pode usar o StrictMode no nível do app ou da atividade.

Ativar caixas de diálogo ANR em segundo plano

O Android mostra caixas de diálogo ANR para apps que levam muito tempo para processar a mensagem de transmissão apenas se Mostrar todos os ANRs estiver ativada nas Opções do desenvolvedor do dispositivo. Por esse motivo, as caixas de diálogo ANR em segundo plano nem sempre são exibidas para o usuário, mas o app ainda pode ter problemas de desempenho.

Traceview

Você pode usar o Traceview para conseguir um trace do seu app em execução enquanto analisa os casos de uso e identifica os lugares em que a linha de execução principal está ocupada. Para mais informações sobre como usar o Traceview, consulte Criação de perfil com o Traceview e o dmtracedump.

Extrair um arquivo de rastreamento

O Android armazena informações de trace quando passa por um ANR. Em versões mais antigas do sistema operacional, há um único arquivo /data/anr/traces.txt no dispositivo. Nas versões mais recentes do SO, há vários arquivos /data/anr/anr_*. Você pode acessar os traces de ANR em um dispositivo ou emulador usando o Android Debug Bridge como root:

adb root
    adb shell ls /data/anr
    adb pull /data/anr/<filename>
    

Você pode capturar um relatório de bug de um dispositivo usando a opção de desenvolvedor "Fazer relatório do problema" no dispositivo ou o comando adb bugreport na sua máquina de desenvolvimento. Para mais informações, consulte Capturar e ler relatórios de bugs.

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 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 movimentada.

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 ofensivo acontece no gerenciador onClick(View), conforme mostrado no exemplo de código a seguir:

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. Para mais informações, consulte Classes auxiliares para linhas de execução. O código a seguir mostra como usar a classe auxiliar AsyncTask para processar a tarefa em uma linha de execução de worker:

Kotlin

    override fun onClick(v: View) {
        // The long-running operation is run on a worker thread
        object : AsyncTask<Array<Int>, Int, Long>() {
            override fun doInBackground(vararg params: Array<Int>): Long? =
                BubbleSort.sort(params[0])

        }.execute(data)
    }
    

Java

    @Override
    public void onClick(View view) {
       // The long-running operation is run on a worker thread
       new AsyncTask<Integer[], Integer, Long>() {
           @Override
           protected Long doInBackground(Integer[]... params) {
               BubbleSort.sort(params[0]);
           }
       }.execute(data);
    }
    

O Traceview mostra que a maior parte do código é executada em uma linha de execução de worker, como mostrado na figura 3. A linha de execução principal está disponível para responder aos eventos do usuário.

Figura 3. Linha do tempo do Traceview que mostra o trabalho gerenciado por uma linha de execução de worker.

Figura 3. Linha do tempo do Traceview que mostra o trabalho gerenciado por uma linha de execução de worker.

E/S na linha de execução principal

A execução de operações de entrada/saída na linha de execução principal é uma causa comum de operações lentas, o que pode causar ANRs. É 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 mais informações, consulte Realizar operações de rede e Armazenamento de 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 seu trabalho, um ANR pode acontecer.

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

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

Figura 4. 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 estado RUNNABLE se estiver pronta para atualizar a IU e, em geral, é responsiva.

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

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

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

O seguinte rastreamento 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 trace pode ajudar você a localizar o código que bloqueia a linha de execução principal. O código a seguir é responsável por manter o bloqueio da linha de execução principal no trace 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 a seguir. Observe que o uso de wait() e notify() não é um padrão recomendado em Kotlin, que tem os próprios mecanismos para lidar com a simultaneidade. Ao usar o Kotlin, use mecanismos específicos dele, 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 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).

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.

Certifique-se de que os bloqueios sejam mantidos pelo menor tempo possível ou, melhor ainda, avalie se o app precisa da retenção mesmo. 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 a comunicação entre as linhas de execução do worker e as principais.

Impasses

Um impasse ocorre quando uma linha de execução entra em um estado de espera porque um recurso necessário é mantido por outra, que também está aguardando um recurso mantido pela primeira. Se a linha de execução principal do app estiver nessa situação, é provável que ANRs aconteçam.

Os impasses são um fenômeno bem estudado na ciência da computação e existem algoritmos de prevenção que você pode usar para evitá-los.

Para mais informações, consulte Impasse e Algoritmos de prevenção de impasse (link em inglês) na Wikipedia.

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, por meio de broadcast receivers. Um ANR ocorre quando um app demora muito para processar a mensagem de transmissão.

Um ANR ocorre nos seguintes casos:

Seu app deve executar somente operações curtas no método onReceive() de um BroadcastReceiver. No entanto, se o app exigir processamento mais complexo como resultado de uma mensagem de transmissão, você precisará adiar a tarefa para um IntentService.

Você pode usar ferramentas como o Traceview para identificar se seu 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 6. Linha do tempo do Traceview mostrando o trabalho de BroadcastReceiver na linha de execução principal.

Figura 6. 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 método onReceive() do BroadcastReceiver, como mostrado no exemplo a seguir:

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 a seguir 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 7. Linha do tempo do Traceview que mostra a mensagem de transmissão processada em uma linha de execução de worker.

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

Seu broadcast receiver pode usar goAsync() para sinalizar ao sistema que precisa de mais tempo para processar a mensagem. No entanto, chame finish() no objeto PendingResult. 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 corrigirá o ANR se a transmissão estiver em segundo plano. O tempo limite do ANR ainda será válido.

Para mais informações sobre ANRs, consulte Como manter seu app responsivo. Para mais informações sobre linhas de execução, consulte Desempenho de linhas de execução.