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.
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 não seja renderizada, causando frustração ao usuário. Para ver mais informações sobre a linha de execução principal do app, consulte 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()
eService.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 chamastartForeground()
dentro de 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. - Interações do JobScheduler: se um
JobService
não retornar deJobService.onStartJob()
ouJobService.onStopJob()
em alguns segundos, ou se um job iniciado pelo usuário começar e seu app não chamarJobService.setNotification()
alguns segundos depois deJobService.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.
Detectar o problema
Se você já publicou seu app, use o Android vitals para ver informações sobre os ANRs dele. É possível usar outras ferramentas para detectar ANRs, mas as ferramentas de terceiros não os informa em versões mais antigas do Android (Android 10 e anteriores), ao contrário do Android vitals.
Android vitals
O Android vitals pode ajudar você a monitorar e melhorar a taxa de ANRs do seu app medindo várias delas:
- Taxa de ANR: é a porcentagem de usuários ativos por dia que tiveram qualquer tipo de ANR.
- Taxa de ANR percebido pelo usuário: é a porcentagem de usuários ativos por dia
que perceberam pelo menos um ANR. Atualmente, apenas ANRs do
tipo
Input dispatching timed out
são considerados percebidos pelo usuário. - Taxa de ANRs múltiplos: é a porcentagem de usuários ativos por dia que tiveram pelo menos dois ANRs.
Um usuário ativo por dia é um usuário único que usa seu app em um único dia em um único dispositivo, possivelmente em várias sessões. Se o usuário utilizar o app em mais de um dispositivo em um único dia, cada dispositivo vai contribuir separadamente para o número de usuários ativos nesse dia. Se vários usuários usarem o mesmo dispositivo em um único dia, isso vai ser contabilizado como um usuário ativo.
A taxa de ANR percebido pelo usuário é uma das principais métricas, ou seja, ela afeta a possibilidade de descoberta do app no Google Play. Isso é importante porque os ANRs que ela conta sempre ocorrem quando o usuário está engajado com o app, o que causa mais interrupções.
O Google Play definiu dois limites de mau comportamento nessa métrica:
- Limite de mau comportamento geral: pelo menos 0,47% dos usuários ativos por dia percebem um ANR em todos os modelos de dispositivos.
- Limite de mau comportamento por dispositivo: pelo menos 8% dos usuários ativos por dia percebem um ANR em um único modelo de dispositivo.
Se o app exceder o limite de mau comportamento geral, ele provavelmente será mostrado para menos pessoas em todos os dispositivos. Se o app exceder o limite de mau comportamento por dispositivo em alguns modelos, ele provavelmente será mostrado para menos pessoas que usam esse tipo de dispositivo, com um aviso na página "Detalhes do app".
O Android vitals pode enviar alertas pelo Play Console quando o app está apresentando muitos ANRs.
Para ver mais informações sobre como o Google Play coleta dados do Android vitals, consulte a documentação do Play Console.
Diagnosticar ANRs
Há alguns padrões comuns que precisam ser observados ao diagnosticar ANRs:
- O app está fazendo operações lentas envolvendo E/S na linha de execução principal.
- O app está fazendo um cálculo longo na linha de execução principal.
- 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.
- A linha de execução principal está bloqueada e esperando um bloco sincronizado para uma operação longa que está acontecendo em outra linha de execução.
- A linha de execução principal está em um impasse com outra linha, seja em um 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 ver mais informações, consulte Impasse (em inglês) na Wikipédia.
As técnicas a seguir podem ajudar a determinar a causa dos ANRs.
HealthStats
HealthStats
fornece métricas sobre
a integridade do app, coletando informações sobre tempo total de uso do usuário e do sistema, tempo de CPU,
rede, estatísticas de rádio, tempo com a tela ativada e desativada e alarmes de ativação. Com esse elemento,
é possível medir o uso geral de CPU e o consumo de bateria.
Debug
Debug
ajuda a inspecionar aplicativos Android
durante o desenvolvimento, fazendo uso de rastreamento e contagens de alocação para identificar instabilidade
e pontos de atraso nos apps. Também é possível usar Debug
para receber contadores relacionados à memória
nativa e ao uso durante a execução, além de métricas de memória que podem ajudar a identificar o consumo
por um processo específico.
ApplicationExitInfo
ApplicationExitInfo
está disponível
no Android 11 (nível 30 da API) e versões mais recentes e fornece informações sobre o
motivo do encerramento do app. Os motivos incluem ANRs, pouca memória, falhas no app,
uso excessivo da CPU, interrupções do usuário, interrupções do sistema ou mudanças na permissão
de execução.
Modo restrito
Usar o StrictMode
ajuda você a encontrar
operações acidentais de E/S na linha de execução principal durante o desenvolvimento do app.
Você pode usar StrictMode
no nível do aplicativo 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 você ativar Mostrar todos os ANRs nas Opções do desenvolvedor do dispositivo. Por esse motivo, as caixas de diálogo ANR em segundo plano nem sempre são mostradas para o usuário, mas o app ainda pode ter problemas de desempenho.
Traceview
Você pode usar o Traceview para extrair um rastro do 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 rastros quando passa por um ANR. Em versões mais antigas do SO,
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 rastros de ANR em um dispositivo ou emulador usando o
Android Debug Bridge como raiz:
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
Você pode capturar um relatório de bug de um dispositivo físico usando a opção de desenvolvedor "Criar relatório do problema" no dispositivo ou o comando adb bugreport na 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 do Traceview em que a linha de execução principal está ocupada por mais de cinco segundos.
A Figura 2 mostra que a maior parte do código com problemas acontece no
gerenciador onClick(View)
,
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. É 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 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 está 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
estado RUNNABLE
(executável)
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
estado BLOCKED
(bloqueada)
e não pode responder aos eventos. O estado é mostrado no Android Device Monitor como
Monitor ou Wait, conforme mostrado na Figura 5.
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).
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.
Impasses
Um impasse ocorre quando uma linha de execução entra em um estado de espera porque um recurso necessário é retido por outra linha, que também está aguardando um recurso retido 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 os evitar.
Para mais informações, consulte Impasse e Algoritmos de prevenção de impasse na Wikipedia (em inglês).
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 falha ao chamarfinish()
no(a)PendingResult
objeto.
Seu app só deve executar operações curtas no
onReceive()
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 um
IntentService
.
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.
Esse comportamento pode ser causado pela execução de operações de longa duração no método
onReceive()
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
uma IntentService
, porque ela usa uma linha de execução de worker para executar
o trabalho. O código abaixo mostra como usar uma
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); } }
Com o uso da
IntentService
, a operação de
longa duração ocorre em uma linha de execução de worker em vez de na linha principal. A figura 7
mostra o trabalho adiado para a linha de execução de worker na linha do tempo do Traceview.
Seu broadcast receiver pode usar
goAsync()
para sinalizar ao sistema que precisa de mais tempo para processar a mensagem. Entretanto,
é preciso chamar
finish()
no
objeto
PendingResult
. O exemplo abaixo 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.
GameActivity
A biblioteca GameActivity
reduziu os ANRs em
estudos de caso de jogos e apps escritos
em C ou C++. Se você substituir a atividade nativa existente por GameActivity
,
poderá reduzir o bloqueio de linhas de execução de interface e impedir que alguns ANRs ocorram.
Para mais informações sobre ANRs, consulte Como manter seu app responsivo. Para mais informações sobre linhas de execução, consulte Performance de linhas de execução.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Ativações excessivas