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 doWebView
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.
WebView
.Pausa do Unity
Quando UnityPlayerActivity
recebe uma chamada onPause()
, a seguinte cadeia de
operações é iniciada:
UnityPlayerActivity
notifica o mecanismo de execução do Unity que a atividade foi pausada.- O Unity chama cada
MonoBehaviour
que implementa o eventoOnApplicationPause
. - O Unity interrompe os componentes e módulos, como reprodução de som, renderização, loop de jogo e animação.
- Para garantir que o
Unity Android Player
(UAP) e o mecanismo sejam sincronizados, o UAP aguarda quatro segundos para que o mecanismo seja interrompido. - 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 umStopwatch
. - Evite operações de E/S ou solicitações de rede síncronas.
- Mova as operações para outro
Thread
usando oTask
. O Unity 2023.1 oferece suporte a um modelo de programação assíncrona simplificado usando as palavras-chaveasync
eawait
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.
- Se o código não depender do contexto da linha de execução principal do Unity, use
- 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.
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:
- Depurar ANRs: desenvolvimento de jogos Android
- ANRs: qualidade do app