Causes de l'ANR et comment l'éviter

ANR (Application Not Responding) - Une erreur qui se produit lorsque l'application ne répond pas. Par conséquent, une boîte de dialogue s'ouvre invitant l'utilisateur à attendre ou à fermer l'application.
image alt

Conditions ANR


  • Les événements d'entrée (boutons et événements tactiles) ne sont pas traités pendant 5 secondes;
  • BroadcastReceiver (onRecieve ()) n'a pas été traité dans le délai spécifié (premier plan - 10 s, arrière-plan - 60 s);
  • ContentProvider n'est pas terminé dans les 10 secondes.

Habituellement, le thread principal est bloqué.

Si vous lisez mes articles, vous êtes probablement déjà habitué au fait que nous explorons le code source. Voyons donc à quoi ressemble l' ANR sous le capot .

La classe AppErrors gère non seulement l'ANR, mais également d'autres erreurs qui peuvent se produire dans l'application, y compris un crash. La méthode handleShowAnrUi () ouvre juste cette fenêtre effrayante pour de nombreux développeurs et utilisateurs qui affiche 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(); } } ... 

Cependant, l'ANR ne démarre pas ici. Comme je l'ai dit ci-dessus, l'une des premières causes de cette erreur est le retard de l'événement d'entrée, qui est de 5 secondes. Par une courte recherche, nous pouvons trouver où cette valeur est définie.

 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 

Maintenant, nous pouvons regarder dans le code où la partie native est appelée. Cela se produit dans la classe InputManagerService .

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

Et voici les mWindowManagerCallbacks dans 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 } 

Examinons de plus près inputDispatchingTimedOut (). Ici, nous montrons simplement le message via l'ActivityManager sur l'expiration du délai d'expiration et laissons l'utilisateur décider d'annuler l'action ou de continuer à attendre. Et c'est dans ActivityManagerService que AppErrors est appelé en cas de plantage ou d'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(); } 

Les principales causes de l'ANR


  • Verrouillage entrée / sortie
  • Congestion du réseau
  • Blocage du fil
  • Boucle sans fin
  • La logique métier prend trop de temps

Éviter l'ANR


  • Le thread d'interface utilisateur principal exécute la logique associée uniquement à l'interface utilisateur;
  • Des calculs complexes (par exemple, opérations de base de données, opérations d'entrée-sortie, opérations de réseau, etc.) sont effectués dans un flux distinct;
  • Utilisez le gestionnaire pour interagir entre le thread d'interface utilisateur et le flux de travail;
  • Utilisez RxJava etc. pour gérer les opérations asynchrones.

Comment attraper ANR


  • Les informations sur ANR peuvent être stockées dans le fichier /data/anr/traces.txt, ou dans un autre chemin / data / anr / anr_ *. Vous pouvez l'obtenir en utilisant les commandes suivantes:

     adb root adb shell ls /data/anr adb pull /data/anr/<filename> 
  • Utilisez le projet open source ANR-WatchDog pour détecter ANR
  • Voir Éviter ANR :)

PS Je publie toutes les sélections dans la chaîne de télégramme @paradisecurity .

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


All Articles