Causas de ANR y cómo evitarlo

ANR (la aplicación no responde): error que ocurre cuando la aplicación no responde. Como resultado, se abre un cuadro de diálogo que solicita al usuario que espere o cierre la aplicación.
imagen alt

Condiciones ANR


  • Los eventos de entrada (botones y eventos táctiles) no se procesan durante 5 segundos;
  • BroadcastReceiver (onRecieve ()) no se procesó dentro del tiempo especificado (primer plano - 10 s, fondo - 60 s);
  • ContentProvider no se completa en 10 segundos.

Por lo general, el hilo principal está bloqueado.

Si lees mis artículos, probablemente ya estés acostumbrado al hecho de que rastreamos el código fuente. Así que veamos cómo se ve el ANR debajo del capó .

La clase AppErrors maneja no solo ANR, sino también otros errores que pueden ocurrir en la aplicación, incluido el bloqueo. El método handleShowAnrUi () simplemente abre esta ventana aterradora para muchos desarrolladores y usuarios que muestra 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(); } } ... 

Sin embargo, ANR no comienza aquí. Como dije anteriormente, una de las primeras causas de este error es el retraso del evento de entrada, que es de 5 segundos. Mediante una breve búsqueda podemos encontrar dónde se establece este valor.

 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 

Ahora podemos buscar en el código donde se llama la parte nativa. Esto sucede en la clase InputManagerService .

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

Y aquí está el mWindowManagerCallbacks en 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 } 

Echemos un vistazo más de cerca a inputDispatchingTimedOut (). Aquí solo mostramos el mensaje a través del ActivityManager sobre el vencimiento del tiempo de espera y dejamos que el usuario decida si cancela la acción o continúa esperando. Y es en el ActivityManagerService que se llama AppErrors en caso de bloqueo o 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(); } 

Las principales causas de ANR


  • Bloqueo de entrada / salida
  • Congestión de red
  • Hilo de bloqueo
  • Bucle sin fin
  • La lógica empresarial lleva demasiado tiempo

Evitar ANR


  • El hilo principal de la interfaz de usuario ejecuta la lógica asociada solo con la interfaz de usuario;
  • Los cálculos complejos (por ejemplo, operaciones de bases de datos, operaciones de entrada-salida, operaciones de red, etc.) se realizan en una secuencia separada;
  • Use el controlador para interactuar entre el hilo de la interfaz de usuario y el flujo de trabajo;
  • Use RxJava, etc. para manejar operaciones asincrónicas.

Cómo atrapar ANR


  • La información sobre ANR se puede almacenar en el archivo /data/anr/traces.txt, o en una ruta / data / anr / anr_ * diferente. Puede obtenerlo usando los siguientes comandos:

     adb root adb shell ls /data/anr adb pull /data/anr/<filename> 
  • Utilice el proyecto de código abierto ANR-WatchDog para detectar ANR
  • Ver Evitar ANR :)

PD: publico todas las selecciones en el canal de telegramas @paradisecurity .

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


All Articles