ANRs comuns de jogos do Unity

Os ANRs do Unity ocorrem por vários motivos. Os ANRs mais comuns são causados pelo uso indevido de componentes do Android e do Unity, além da falha de comunicação deles.

WebView

WebView é uma classe do Android que mostra páginas da Web. Os SDKs de terceiros (como anúncios) usam WebView para mostrar conteúdo dinâmico da Web em atividades diferentes da UnityPlayerActivity. Os ANRs ocorrem quando SDKs de terceiros usam WebView de forma indevida.

Stack trace

O stack trace é seu primeiro recurso para entender a causa do ANR.

/data/app/~~p-0ksfCD6bF6Sdq6kpVePg==/com.google.android.webview-5YQZOqKbbqp-uoLY6WYnTw==/base.apk!libmonochrome.so
  at J.N.Mhc_M_H$ (Native method)
  at org.chromium.components.viz.service.frame_sinks.ExternalBeginFrameSourceAndroid.doFrame (chromium-TrichromeWebViewGoogle.aab-stable-579013831:60)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1054)
  at android.view.Choreographer.doCallbacks (Choreographer.java:878)
  at android.view.Choreographer.doFrame (Choreographer.java:807)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1041)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7721)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:952)

Figura 1.stack trace de ANR causado por uma espera futex.

Causa

Até o momento, a causa raiz desse problema não está clara. Algumas possíveis causas podem incluir:

  • Implementação de anúncio incorreta.
  • Uma versão desatualizada do WebView, já que o usuário pode ter optado por não atualizar o app automaticamente.
  • Uso elevado de recursos do sistema (CPU, GPU etc.), o que pode exigir muita criação de perfil.
  • A compilação do sombreador falha, o que pode indicar que o conteúdo tem um sombreador incompatível ou que o usuário tem uma versão antiga do WebView instalada.

Solução

  • Para restringir o tipo de conteúdo que está fazendo com que a WebView bloqueie a linha de execução principal, adicione registros ao jogo sempre que uma página da Web for carregada, exibida ou fechada.
    • É possível usar os serviços de relatórios Backtrace ou Crashlytics.
    • Em seguida, depois de analisar os dados e encontrar o problema, tente desativar os provedores de anúncios ofensivos.
    • Inclua registros de memória para garantir que o problema não esteja relacionado à memória.
  • Alerte o usuário para atualizar o WebView no Google Play. Do Android 5.0 (nível 21 da API) e versões mais recentes, WebView foi movido para um APK. Portanto, ele pode ser atualizado separadamente da Plataforma Android. Para saber qual versão do WebView está em uso em um dispositivo, acesse Configurações > Apps > WebView do sistema Android e confira a versão na parte de baixo da página.
Tela de informações do app mostrando as versões do WebView.
Figura 1. Verifique a versão do WebView.

Pausa do Unity

Quando UnityPlayerActivity recebe uma chamada onPause(), a seguinte cadeia de operações é iniciada:

  1. UnityPlayerActivity notifica o mecanismo de execução do Unity que a atividade foi pausada.
  2. O Unity chama cada MonoBehaviour que implementa o evento OnApplicationPause.
  3. O Unity interrompe os componentes e módulos, como reprodução de som, renderização, loop de jogo e animação.
  4. Para garantir que o Unity Android Player (UAP) e o mecanismo sejam sincronizados, o UAP aguarda quatro segundos para que o mecanismo seja interrompido.
  5. Se essa operação levar mais de cinco segundos, o sistema vai acionar um ANR.

Stack trace

"main" tid=1 Timed Waiting
jdk.internal.misc.Unsafe.park (Native method)
java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:234)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos (AbstractQueuedSynchronizer.java:1079)
java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos (AbstractQueuedSynchronizer.java:1369)
java.util.concurrent.Semaphore.tryAcquire (Semaphore.java:415)
com.unity3d.player.UnityPlayer.pauseUnity (UnityPlayer.java:833)
com.unity3d.player.UnityPlayer.pause (UnityPlayer.java:796)
com.unity3d.player.UnityPlayerActivity.onPause (UnityPlayerActivity.java:117)
android.app.Activity.performPause (Activity.java:8517)
android.app.Instrumentation.callActivityOnPause (Instrumentation.java:1618)
android.app.ActivityThread.performPauseActivityIfNeeded (ActivityThread.java:5061)
android.app.ActivityThread.performPauseActivity (ActivityThread.java:5022)
android.app.ActivityThread.handlePauseActivity (ActivityThread.java:4974)
android.app.servertransaction.PauseActivityItem.execute (PauseActivityItem.java:48)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:179)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2303)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:201)
android.os.Looper.loop (Looper.java:288)
android.app.ActivityThread.main (ActivityThread.java:7884)
java.lang.reflect.Method.invoke (Native method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)

Figura 3. ANR causado por um sinal semáforo que nunca é lançado.

Solução

Verifique se o código do jogo em C# não demora muito para terminar a execução durante um evento de pausa ou retomada.

  • Crie o perfil do seu jogo e confira se o OnApplicationPause é uma operação cara. Você pode usar um Stopwatch.
  • Evite operações de E/S ou solicitações de rede síncronas.
  • Mova as operações para outro Thread usando o Task. O Unity 2023.1 oferece suporte a um modelo de programação assíncrona simplificado usando as palavras-chave async e await do C#.

UnitySendMessage bloqueado

Os plug-ins e os SDKs do Java para Unity enviam dados para a camada do jogo em C# usando a JNI (link em inglês). No entanto, essa comunicação pode bloquear a linha de execução principal devido a uma rotina de sincronização nativa, como um mutex, causando um ANR devido à contenção de bloqueio.

Stack trace

O ANR na figura 4 foi causado por uma operação longa no código C# chamada por um plug-in Java. O mecanismo do Unity usa um mutex de herança sem prioridade para garantir a execução correta.

libc.so NonPI::MutexLockWithTimeout(pthread_mutex_internal_t*, bool, timespec const*) + 604
com.unity3d.player.UnityPlayer.nativeUnitySendMessage (Native method)
com.unity3d.player.UnityPlayer.UnitySendMessage (UnityPlayer.java:665)

Figura 4. ANR causado por uma contenção de bloqueio.

Causa

O problema é que várias mensagens estão sendo despachadas quando o aplicativo é retomado. As mensagens são colocadas na fila porque não podem ser enviadas enquanto o jogo está em segundo plano. As mensagens são todas despachadas simultaneamente quando o app é retomado.

Durante um período de pausa, geralmente as informações do jogo são armazenadas no servidor. Por exemplo, você registra a posição de um jogador para que ele possa retornar ao mesmo lugar quando for retomado.

Essa carga de trabalho, combinada com outro código de terceiros que cria a própria carga de trabalho, pode sobrecarregar os recursos do dispositivo, principalmente a linha de execução principal. A linha de execução principal executa a interface do usuário de um app e costuma ser o local principal dos ANRs. Portanto, qualquer carga de trabalho adicionada à linha de execução principal aumenta o potencial de um ANR.

Solução

Durante uma pausa do app, verifique se todas as ações de código são necessárias ou tente salvar o estado do usuário na memória local do dispositivo. E, é claro, veja se você também pode concluir essas ações fora do período de pausa.

Algumas abordagens:

  • Mova a operação C# que processa uma mensagem para uma linha de execução que não seja a principal.
    • Se o código não depender do contexto da linha de execução principal do Unity, use Task para comunicação em vez de mensagem.
  • Não envie várias mensagens do plug-in quando o jogo estiver pausado.
    • O mecanismo não pode enviar mensagens enquanto o jogo está em segundo plano.
    • Só envie o último estado de dados ao jogo se isso não afetar a funcionalidade dele.

Instalar o referenciador

Referenciador de instalação do Google Play é uma string exclusiva enviada à Play Store sempre que um usuário clica em um anúncio. É um identificador de acompanhamento de anúncios específico para Android. Depois de instalado, o app envia o referenciador de instalação ao parceiro de atribuição, que corresponde à origem à instalação (atribuindo a conversão).

Stack trace

A Figura 5 mostra um stack trace de ANR de um jogo que usa o SDK do Facebook para extrair a atribuição de instalação.

Figura 5. Relatório do Android vitals que contém uma chamada de vinculação.

Causa

O ANR foi causado por uma chamada de vinculação lenta. No entanto, a causa raiz não pode ser determinada sem acesso ao código-fonte do SDK.

Solução

Resolver esse tipo de problema envolve a comunicação com o desenvolvedor do SDK ou muitas pesquisas on-line sobre uma possível solução, verificar se uma versão mais recente do SDK resolve o ANR para outras pessoas ou até mesmo testar uma pequena estratégia de lançamento.

O Google oferece uma página do SDK Index que combina dados de uso de apps do Google Play com informações coletadas pela detecção de código para fornecer atributos e indicadores projetados para ajudar você a decidir se quer adotar, manter ou remover um SDK do seu app.

Outros recursos

Para saber mais sobre ANRs, consulte os seguintes recursos: