Causas de ANR e como evitá-lo

ANR (Aplicativo Não Responde) - Um erro que ocorre quando o aplicativo não responde. Como resultado, uma caixa de diálogo é aberta solicitando que o usuário aguarde ou feche o aplicativo.
imagem alt

Condições ANR


  • Eventos de entrada (botões e eventos de toque) não são processados ​​por 5 segundos;
  • O BroadcastReceiver (onRecieve ()) não foi processado dentro do tempo especificado (primeiro plano - 10 s, plano de fundo - 60 s);
  • O ContentProvider não é concluído em 10 segundos.

Normalmente, o segmento principal está bloqueado.

Se você lê meus artigos, provavelmente já está acostumado com o fato de rastrearmos o código-fonte. Então, vamos ver como é o ANR sob o capô .

A classe AppErrors controla não apenas o ANR, mas também outros erros que podem ocorrer no aplicativo, incluindo falha. O método handleShowAnrUi () apenas abre essa janela assustadora para muitos desenvolvedores e usuários que exibem ANR.

class AppErrors { ... void handleShowAnrUi(Message msg) { Dialog dialogToShow = null; synchronized (mService) { AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj; final ProcessRecord proc = data.proc; if (proc == null) { Slog.e(TAG, "handleShowAnrUi: proc is null"); return; } if (proc.anrDialog != null) { Slog.e(TAG, "App already has anr dialog: " + proc); MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.ALREADY_SHOWING); return; } Intent intent = new Intent("android.intent.action.ANR"); if (!mService.mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); } mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; if (mService.canShowErrorDialogs() || showBackground) { dialogToShow = new AppNotRespondingDialog(mService, mContext, data); proc.anrDialog = dialogToShow; } else { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.CANT_SHOW); // Just kill the app if there is no dialog to be shown. mService.killAppAtUsersRequest(proc, null); } } // If we've created a crash dialog, show it without the lock held if (dialogToShow != null) { dialogToShow.show(); } } ... 

No entanto, o ANR não inicia aqui. Como eu disse acima, uma das primeiras causas desse erro é o atraso do evento de entrada, que é de 5 segundos. Por uma breve pesquisa, podemos encontrar onde esse valor está definido.

 namespace android { // Default input dispatching timeout if there is no focused application or paused window // from which to determine an appropriate dispatching timeout. const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec 

Agora podemos procurar o código onde a parte nativa é chamada. Isso acontece na classe InputManagerService .

  // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) { return mWindowManagerCallbacks.notifyANR( inputApplicationHandle, inputWindowHandle, reason); } 

E aqui está o mWindowManagerCallbacks no InputMonitor :

  if (appWindowToken != null && appWindowToken.appToken != null) { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. final AppWindowContainerController controller = appWindowToken.getController(); final boolean abort = controller != null && controller.keyDispatchingTimedOut(reason, (windowState != null) ? windowState.mSession.mPid : -1); if (!abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return appWindowToken.mInputDispatchingTimeoutNanos; } } else if (windowState != null) { try { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. long timeout = ActivityManager.getService().inputDispatchingTimedOut( windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return timeout * 1000000L; // nanoseconds } } catch (RemoteException ex) { } } return 0; // abort dispatching } 

Vamos dar uma olhada em inputDispatchingTimedOut (). Aqui, apenas mostramos a mensagem no ActivityManager sobre a expiração do tempo limite e permitimos que o usuário decida se deseja cancelar a ação ou continuar aguardando. E é no ActivityManagerService que AppErrors é chamado em caso de falha ou ANR.

  private boolean makeAppCrashingLocked(ProcessRecord app, String shortMsg, String longMsg, String stackTrace) { app.crashing = true; app.crashingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, String activity, String shortMsg, String longMsg) { app.notResponding = true; app.notRespondingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, activity, shortMsg, longMsg, null); startAppProblemLocked(app); app.stopFreezingAllLocked(); } 

As principais causas de ANR


  • Bloqueio de entrada / saída
  • Congestionamento de rede
  • Bloqueio de segmentos
  • Loop infinito
  • A lógica de negócios leva muito tempo

Evitando ANR


  • O encadeamento principal da interface do usuário executa a lógica associada apenas à interface do usuário;
  • Cálculos complexos (por exemplo, operações de banco de dados, operações de entrada e saída, operações de rede etc.) são executados em um fluxo separado;
  • Use o manipulador para interagir entre o thread da interface do usuário e o fluxo de trabalho;
  • Use RxJava etc. para manipular operações assíncronas.

Como pegar ANR


  • As informações sobre ANR podem ser armazenadas no arquivo /data/anr/traces.txt ou em um caminho / data / anr / anr_ * diferente. Você pode obtê-lo usando os seguintes comandos:

     adb root adb shell ls /data/anr adb pull /data/anr/<filename> 
  • Use o projeto de código aberto ANR-WatchDog para detectar ANR
  • Consulte Evitando ANR :)

PS Publico todas as seleções no canal de telegrama @paradisecurity .

Source: https://habr.com/ru/post/pt439086/


All Articles