In diesem Abschnitt wird gezeigt, wie Sie mit ProfilingManager einen ANR-Fehler (Application Not Responding) beheben können. Dazu wird ein Beispiel-Trace verwendet.
App für die Erfassung von ANR-Fehlern einrichten
Richten Sie zuerst einen ANR-Trigger in Ihrer App ein:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
Nachdem Sie einen ANR-Trace erfasst und hochgeladen haben, öffnen Sie ihn in der Perfetto-UI.
Trace analysieren
Da der ANR den Trace ausgelöst hat, wissen Sie, dass der Trace beendet wurde, als das System im Hauptthread Ihrer App eine Reaktionsträgheit erkannt hat. Abbildung 1 zeigt, wie Sie zum Hauptthread Ihrer App navigieren, der in der Benutzeroberfläche entsprechend gekennzeichnet ist.
Das Ende des Traces entspricht dem Zeitstempel des ANR, wie in Abbildung 2 dargestellt.
Der Trace enthält auch Vorgänge, die von der App ausgeführt wurden, als der ANR-Fehler aufgetreten ist.
Konkret wurde in der App Code im Trace-Slice handleNetworkResponse ausgeführt. Dieser Ausschnitt befand sich im Ausschnitt MyApp:SubmitButton. Dabei wurden 1,48 Sekunden CPU-Zeit verwendet (Abbildung 3).
Wenn Sie sich beim Debuggen ausschließlich auf Stacktraces zum Zeitpunkt des ANR-Fehlers verlassen, weisen Sie den ANR-Fehler möglicherweise fälschlicherweise vollständig dem Code zu, der im handleNetworkResponse-Trace-Abschnitt ausgeführt wird, der noch nicht beendet war, als die Profilaufzeichnung abgeschlossen wurde. 1,48 Sekunden reichen jedoch nicht aus, um allein einen ANR-Fehler auszulösen, auch wenn es sich um einen aufwendigen Vorgang handelt. Sie müssen weiter in die Vergangenheit zurückblicken, um zu verstehen, was den Hauptthread vor dieser Methode blockiert hat.
Um einen Ausgangspunkt für die Suche nach der Ursache für den ANR zu finden, suchen wir nach dem letzten Frame, der vom UI-Thread generiert wurde. Dieser entspricht dem Choreographer#doFrame 551275-Abschnitt. Vor dem Start des MyApp:SubmitButton-Abschnitts, der im ANR endete, gibt es keine großen Verzögerungsquellen (Abbildung 4).
Um die Verstopfung zu verstehen, zoomen Sie heraus, um den gesamten MyApp:SubmitButton-Schnitt zu sehen. Wie in Abbildung 4 zu sehen ist, verbringt der Thread 75% der Zeit (6,7 Sekunden) im Status Sleeping und nur 24% der Zeit im Status Running.
Das bedeutet, dass die Hauptursache für den ANR-Fehler das Warten und nicht die Berechnung war. Untersuche die einzelnen Schlafphasen, um ein Muster zu erkennen.
Die ersten drei Schlafintervalle (Abbildungen 6–8) sind nahezu identisch und dauern jeweils etwa 2 Sekunden. Der vierte Schlaf (Abbildung 9) ist mit 0,7 Sekunden ein Ausreißer. Eine Dauer von genau 2 Sekunden ist in einer Computerumgebung selten ein Zufall. Dies deutet stark auf ein programmiertes Zeitlimit und nicht auf zufällige Ressourcenkonflikte hin. Der letzte Ruhezustand kann dadurch verursacht werden, dass der Thread das Warten beendet, weil der Vorgang, auf den er gewartet hat, erfolgreich war.
Die Hypothese lautet, dass die App mehrmals ein nutzerdefiniertes Zeitlimit von 2 Sekunden erreicht hat und schließlich erfolgreich war, was zu einer Verzögerung geführt hat, die ein ANR-Ereignis ausgelöst hat.
Prüfen Sie dazu den Code, der mit dem Abschnitt MyApp:SubmitButton-Trace verknüpft ist:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
Der Code bestätigt diese Hypothese. Die Methode onClickSubmit führt eine Netzwerkanfrage im UI-Thread mit einem fest codierten NETWORK_TIMEOUT_MILLISECS von 2.000 ms aus.
Wichtig ist, dass sie in einer while-Schleife ausgeführt wird, die bis zu zehn Mal wiederholt wird.
In diesem speziellen Trace hatte der Nutzer wahrscheinlich eine schlechte Netzwerkverbindung. Die ersten drei Versuche sind fehlgeschlagen, was zu drei Zeitüberschreitungen von jeweils 2 Sekunden (insgesamt 6 Sekunden) geführt hat.
Der vierte Versuch war nach 0,7 Sekunden erfolgreich, sodass der Code mit handleNetworkResponse fortfahren konnte. Die akkumulierte Wartezeit hat jedoch bereits den ANR ausgelöst.
Vermeiden Sie diese Art von ANR-Fehler, indem Sie netzwerkbezogene Vorgänge mit unterschiedlichen Latenzen in einem Hintergrundthread ausführen, anstatt im Hauptthread. So bleibt die Benutzeroberfläche auch bei schlechter Verbindung reaktionsfähig und diese Art von ANR-Fehlern wird vollständig vermieden.