Detectar um anúncio intersticial travado no Android com eventos
Por Nesim Razon, desenvolvedor Android independente · 2026-11-12
Um jogador escreveu que, depois de um anúncio intersticial, o jogo parou de responder aos toques. Não havia falha no Crashlytics, nem ANR, e não conseguíamos reproduzir nos nossos próprios celulares. Este texto explica como adicionamos um pequeno registro do ciclo de vida em torno dos anúncios em tela cheia do AdMob, como os dados se parecem e o que fizemos com eles. É uma história de depuração, não uma receita que sirva para todo aplicativo.
O relato que não tinha rastreamento de pilha
O relato veio de um único usuário com um celular Redmi com HyperOS e Android 13. No fim de uma fase apareceu um anúncio em tela cheia. Quando ele acabou, o jogo parecia normal, mas ignorava o jogador. Os toques pareciam cair em outro lugar: o painel da Play Store abria sozinho e o botão da próxima fase não fazia nada. O jogador contou que já tinha acontecido uma vez com o mesmo tipo de anúncio.
Visto de fora, parece um travamento, mas o aplicativo não estava travado. A thread principal funcionava e nada lançava exceção. É exatamente por isso que as ferramentas habituais ficaram em silêncio.
Por que o relatório de falhas não enxerga isso
O Crashlytics registra exceções fatais e, se você pedir, também as não fatais. Um anúncio travado não é nenhuma das duas. O SDK de anúncios executa sua própria atividade e uma web view; se o conteúdo se comporta mal, a janela pode ficar por cima do jogo como uma camada transparente. Seu código nunca falha. O usuário apenas não consegue alcançá-lo.
Os relatórios de ANR também não ajudam, porque o aplicativo continua respondendo aos eventos de entrada. A única parte que sabe que algo deu errado é o SDK de anúncios, e ele não nos avisou. Então a primeira tarefa foi tornar visível o que era invisível: anotar, em dispositivos reais e em ordem, o que o anúncio fazia.
A ideia: registrar cada etapa do anúncio
O AdMob oferece callbacks para o carregamento e para o conteúdo em tela cheia. Ligamos um pequeno registrador a cada um deles, tanto no formato intersticial quanto no premiado. Cada chamada gera um evento do Firebase Analytics chamado ad_event com estes parâmetros: format (interstitial ou rewarded), stage, adapter, code e ms.
As etapas no nosso código são loaded, load_failed, shown, impression, clicked, dismissed, show_failed e mais uma que inventamos, resumed_no_dismiss. O adapter é o nome da classe do adaptador de mediação que entregou o anúncio, cortado nos últimos 60 caracteres, de modo que um único evento mostra qual fonte de anúncios participou. O campo code leva o código de erro do SDK nas falhas, e ms é o tempo desde que o anúncio foi exibido. A mesma linha também é gravada como rastro no Crashlytics, então, se uma falha acontecer depois, o relatório dela mostra qual etapa do anúncio veio antes.
A única etapa que o SDK não fornece
Todo anúncio normal termina com um callback dismissed. Um anúncio travado é o caso em que esse callback nunca chega, então não dá para registrá-lo a partir de um callback. É preciso perceber a ausência.
Resolvemos isso com o ciclo de vida do aplicativo. Quando um anúncio é exibido, o encapsulador guarda o formato, o adaptador e a hora de início. Depois, um ouvinte global do ciclo de vida das atividades observa onActivityResumed. Se uma das nossas atividades voltar ao primeiro plano enquanto o encapsulador ainda acredita que há um anúncio na tela, o anúncio deveria ter fechado, mas não nos avisou. Ignoramos as atividades do próprio SDK de anúncios, exigimos pelo menos 1,5 segundo desde a exibição para evitar condições de corrida e notificamos só uma vez por anúncio. O resultado é um evento resumed_no_dismiss com o tempo decorrido. O ouvinte apenas registra. Nunca mexe no jogo, então não pode piorar nada.
Transformar eventos em uma tabela
Eventos brutos não são um diagnóstico. Buscamos os números com a API de dados do GA4 em um relatório semanal. Para isso funcionar, format e stage precisam estar registrados no GA4 como dimensões personalizadas com escopo de evento; senão a consulta retorna erro. O relatório agrupa por formato e etapa e mostra dois números em cada célula: quantos eventos e quantos usuários.
Lendo uma coluna de cima para baixo, você vê a vida de um anúncio: loaded, shown, impression, dismissed. O formato saudável é que dismissed fique próximo de shown. O relatório acrescenta mais uma linha: os eventos de travamento divididos pelos de exibição, em porcentagem. O painel avisa quando a contagem de resumed_no_dismiss é maior que zero e indica as ferramentas de revisão de anúncios do AdMob. O par a procurar é shown sem dismissed, e o evento resumed_no_dismiss o torna explícito.
O que encontramos e o que mudamos
A descrição do usuário apontava para um anúncio específico de um jogo de terceiros que às vezes deixava uma camada transparente na tela. Como o anúncio chegava pelo caminho normal do AdMob, a correção ficou do lado da demanda e não no nosso código: bloqueamos o domínio desse anunciante nos controles de bloqueio do AdMob. Nessa mesma versão, de propósito, não mudamos a forma como o jogo exibe anúncios. Primeiro medimos, para que uma correção posterior pudesse ser comparada com uma linha de base.
Vale ser honesto sobre as evidências. O primeiro sinal foi o relato de um único usuário, mais o fato de que o mesmo anúncio já tinha dado problema. O registro foi adicionado para saber com que frequência o problema ocorre e qual adaptador está por trás; o bloqueio foi uma resposta direcionada ao anúncio que o usuário descreveu. Não tenho um experimento controlado de antes e depois, e com poucos jogadores os números são pequenos. Trate nossos números como um alarme de fumaça, não como uma medição com intervalo de confiança.
Limites e privacidade
A heurística pode gerar falsos positivos. Um dispositivo que traga nossa atividade de volta ao primeiro plano por outro motivo enquanto o encapsulador ainda acha que há um anúncio poderia ser contado; por isso existem o limite de tempo e a regra de uma vez por anúncio. Ela também pode deixar passar um anúncio travado se o usuário forçar o fechamento do aplicativo. Além disso, o SDK amostra e agrupa os eventos do Analytics, então os números chegam com um ou dois dias de atraso.
Quanto à privacidade: o evento leva apenas o formato, a etapa, o nome da classe do adaptador cortado, um código de erro e uma duração. Não há identificador de usuário nos nossos parâmetros e o comentário no código afirma que o ID de publicidade não é coletado. O Analytics só fica ativo na versão de lançamento, para que os testes não sujem os números. Para os anúncios, usamos também o fluxo habitual de consentimento da plataforma de mensagens do usuário do Google. Se o seu aplicativo coleta análise em regiões com regras de consentimento, verifique como a sua configuração limita o Firebase Analytics; aqui não verificamos uma configuração específica do modo de consentimento.
O que levar daqui
Se você exibe anúncios, provavelmente tem um ponto cego entre exibir e fechar. Algumas dezenas de linhas podem fechá-lo: registre cada callback, deduza o que falta a partir do ciclo de vida das atividades e represente o formato por etapa. Mantenha o registrador passivo. Relate o que aconteceu e não tente consertar a tela automaticamente até saber o que está consertando.
Um anúncio travado custa de duas formas. O jogador deixa uma avaliação ruim e você perde a sessão. As duas coisas são difíceis de atribuir sem dados. Com uma tabela de ciclo de vida, o próximo relato de um usuário deixa de ser uma história que você não consegue verificar e passa a ser uma linha que você pode consultar.
Perguntas frequentes
Por que o Crashlytics não mostra um anúncio intersticial travado?
Porque nada falha. A janela do anúncio fica por cima do aplicativo enquanto ele continua funcionando, então não há exceção nem ANR para registrar.
Como detectar um anúncio que nunca fecha?
Guarde a hora em que o anúncio foi exibido e observe o ciclo de vida das atividades. Se uma das suas atividades voltar ao primeiro plano enquanto o anúncio ainda estiver marcado como visível, notifique um evento como resumed_no_dismiss.
Quais parâmetros são registrados em cada evento de anúncio?
O formato do anúncio, a etapa, o nome da classe do adaptador cortado, um código de erro nas falhas e os milissegundos desde que o anúncio foi exibido.
Preciso registrar os parâmetros no GA4?
Sim. Para consultar format e stage pela API de dados do GA4, registre-os como dimensões personalizadas com escopo de evento.
Como o anúncio travado foi resolvido?
Bloqueando o domínio do anunciante no AdMob. O registro não mudou o comportamento; ele serve para medir com que frequência o problema ocorre.
Mais do blog
- Como dar o mesmo quebra-cabeça diário a todos sem servidor
- Como centenas de fases de quebra-cabeça são geradas e verificadas
- Duas dúzias de apps Android mantidas por um só dev
Como garantimos que um Mahjong Paciência tenha solução
História do Mahjong Paciência e a diferença para o Mahjong- Jogo da velha: história de um jogo resolvido e suas variantes