Analyse und Optimierung von App-Start-ups

Beim Start der App hinterlässt deine App den ersten Eindruck bei den Nutzern. Die App muss schnell geladen werden und Informationen anzeigen, die der Nutzer zur Verwendung der App benötigt. Wenn der Start der App zu lange dauert, verlassen Nutzer die App möglicherweise, weil sie zu lange warten.

Wir empfehlen, zum Analysieren des Startvorgangs die MacroBenchmark-Bibliothek zu verwenden. Die Bibliothek bietet einen Überblick und detaillierte System-Traces, um genau zu sehen, was beim Start passiert.

System-Traces bieten nützliche Informationen darüber, was auf Ihrem Gerät geschieht. So können Sie nachvollziehen, was Ihre App beim Start tut, und potenzielle Optimierungsbereiche identifizieren.

So analysieren Sie den App-Start:

Schritte zur Analyse und Optimierung des Start-ups

Anwendungen müssen beim Start häufig bestimmte Ressourcen laden, die für Endnutzer kritisch sind. Nicht notwendige Ressourcen können erst nach Abschluss des Startvorgangs geladen werden.

Berücksichtigen Sie Folgendes, um Abstriche bei der Leistung zu machen:

  • Verwenden Sie die MacroBenchmark-Bibliothek, um die für jeden Vorgang erforderliche Zeit zu messen und Blöcke zu identifizieren, deren Ausführung lange dauert.

  • Prüfen Sie, ob der ressourcenintensive Vorgang für den Anwendungsstart entscheidend ist. Wenn der Vorgang warten kann, bis die Anwendung vollständig gezeichnet ist, kann dies dazu beitragen, die Ressourceneinschränkungen beim Start zu minimieren.

  • Sorgen Sie dafür, dass dieser Vorgang beim Start der Anwendung ausgeführt wird. Häufig können unnötige Vorgänge von Legacy-Code oder Drittanbieterbibliotheken aufgerufen werden.

  • Verschieben Sie lang andauernde Vorgänge nach Möglichkeit in den Hintergrund. Hintergrundprozesse können sich weiterhin auf die CPU-Nutzung beim Start auswirken.

Nachdem Sie den Vorgang vollständig untersucht haben, können Sie sich für einen Kompromiss zwischen der Ladezeit und der Notwendigkeit entscheiden, ihn beim Start der Anwendung zu berücksichtigen. Berücksichtigen Sie auch mögliche Regressionen oder funktionsgefährdende Änderungen, wenn Sie den Workflow Ihrer Anwendung ändern.

Optimieren Sie die Anwendung und messen Sie sie noch einmal, bis Sie mit der Startzeit Ihrer Anwendung zufrieden sind. Weitere Informationen finden Sie unter Probleme mithilfe von Messwerten erkennen und diagnostizieren.

Zeit, die in wichtigen Abläufen aufgewendet wird, messen und analysieren

Wenn Sie ein vollständiges Trace für den Anwendungsstart haben, sollten Sie sich den Trace ansehen und die Zeit für wichtige Vorgänge wie bindApplication oder activityStart messen. Wir empfehlen, Perfetto oder die Android Studio-Profiler zu verwenden, um diese Traces zu analysieren.

Sehen Sie sich die Gesamtzeit des Anwendungsstarts an, um Vorgänge zu ermitteln, die Folgendes bewirken:

  • Sie umfassen einen längeren Zeitraum und können optimiert werden. Jede Millisekunde zählt für die Leistung. Suchen Sie beispielsweise nach Choreographer-Abrufzeiten, Layout-Inflationszeiten, Ladezeiten von Bibliotheken, Binder-Transaktionen oder Ressourcenladezeiten. Sehen Sie sich zuerst alle Vorgänge an, die länger als 20 ms dauern.
  • Hauptthread blockieren. Weitere Informationen finden Sie unter In einem Systrace-Bericht navigieren.
  • Sie müssen beim Start nicht ausgeführt werden.
  • Sie können warten, bis der erste Frame gezeichnet wurde.

Untersuchen Sie jeden dieser Traces genauer, um Leistungslücken zu finden.

Teure Vorgänge im Hauptthread identifizieren

Es empfiehlt sich, teure Vorgänge wie Datei-E/A und Netzwerkzugriff vom Hauptthread fernzuhalten. Dies ist auch beim Start der Anwendung wichtig, da teure Vorgänge im Hauptthread dazu führen können, dass die Anwendung nicht mehr reagiert und andere kritische Vorgänge verzögern. Mit StrictMode.ThreadPolicy können Sie Fälle identifizieren, in denen teure Vorgänge im Hauptthread ausgeführt werden. Es empfiehlt sich, StrictMode bei Builds zur Fehlerbehebung zu aktivieren, um Probleme so früh wie möglich zu identifizieren, wie im folgenden Beispiel gezeigt:

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ...
        if(BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                    new StrictMode.ThreadPolicy.Builder()
                            .detectAll()
                            .penaltyDeath()
                            .build()
            );
        }
        ...
    }
}

Wenn Sie StrictMode.ThreadPolicy verwenden, wird die Thread-Richtlinie in allen Builds zur Fehlerbehebung aktiviert und die Anwendung stürzt immer dann ab, wenn Verstöße gegen die Thread-Richtlinie erkannt werden. Dadurch sind Verstöße gegen die Thread-Richtlinie schwer zu übersehen.

TTID und TTFD

Wenn Sie die Zeit sehen möchten, die die App zum Erstellen des ersten Frames benötigt, messen Sie die Zeit bis zur ersten Anzeige (TTID). Dieser Messwert spiegelt jedoch nicht unbedingt die Zeit wider, bis der Nutzer mit Ihrer Anwendung interagieren kann. Der Messwert Zeit bis zur vollständigen Anzeige (Time to Full Display, TTFD) ist nützlicher, um die Codepfade zu messen und zu optimieren, die für einen vollständig nutzbaren Anwendungsstatus erforderlich sind.

Strategien für die Berichterstellung, wenn die App-UI vollständig gezeichnet ist, finden Sie unter Genauigkeit der Startzeit verbessern.

Optimieren Sie sowohl für TTID als auch für TTFD, da beide in ihrem eigenen Bereich wichtig sind. Eine kurze TTID hilft dem Nutzer zu erkennen, dass die App tatsächlich gestartet wird. Eine kurze TTFD-Datei ist wichtig, damit der Nutzer schnell mit der Anwendung interagieren kann.

Gesamtstatus des Threads analysieren

Wählen Sie die App-Startzeit aus und sehen Sie sich die gesamten Thread-Slices an. Der Hauptthread muss immer responsiv sein.

Tools wie Android Studio Profiler und Perfetto bieten einen detaillierten Überblick über den Hauptthread und darüber, wie viel Zeit in jeder Phase verbracht wird. Weitere Informationen zum Visualisieren von Perfetto-Traces finden Sie in der Dokumentation zur Perfetto-UI.

Wichtige Blöcke des Ruhezustands des Hauptthreads identifizieren

Wenn viel Zeit in den Ruhemodus verbracht wird, liegt dies wahrscheinlich daran, dass der Hauptthread der Anwendung auf den Abschluss der Arbeit wartet. Bei einer Multithread-Anwendung sollten Sie den Thread ermitteln, auf den Ihr Hauptthread wartet, und diese Vorgänge optimieren. Außerdem sollten Sie darauf achten, dass keine unnötigen Sperrenkonflikte zu Verzögerungen im kritischen Pfad führen.

Blockierung des Hauptthreads und unterbrechungsfreien Schlaf reduzieren

Suchen Sie nach allen Instanzen des Hauptthreads, die blockiert werden. In Perfetto und Studio Profiler wird dies durch eine orangefarbene Markierung auf der Zeitachse des Threadstatus angezeigt. Identifizieren Sie die Vorgänge, untersuchen Sie, ob diese zu erwarten sind oder vermieden werden können, und optimieren Sie gegebenenfalls.

Durch I/A-bezogener unterbrechungsfreier Schlaf kann eine wirklich gute Gelegenheit zur Verbesserung sein. Andere Prozesse, die E/A ausführen, können mit der E/A in Konflikt stehen, die die oberste Anwendung ausführt, auch wenn es sich um nicht zusammenhängende Anwendungen handelt.

Startzeit verkürzen

Nachdem Sie eine Optimierungsmöglichkeit identifiziert haben, erkunden Sie mögliche Lösungen zur Verkürzung der Startzeiten:

  • Inhalte werden verzögert und asynchron geladen, um die TTID zu beschleunigen.
  • Minimieren Sie die Aufrufe von Funktionen, die Binder-Aufrufe ausführen. Falls sich diese nicht vermeiden lassen, sollten Sie diese Aufrufe optimieren, indem Sie Werte im Cache speichern, anstatt Aufrufe zu wiederholen oder nicht blockierende Arbeit in Hintergrundthreads zu verschieben.
  • Damit Ihre App schneller gestartet wird, können Sie so schnell wie möglich etwas einblenden, das ein minimales Rendering erfordert, bis der Rest des Bildschirms geladen ist.
  • Erstellen Sie ein Startprofil und fügen Sie es Ihrer Anwendung hinzu.
  • Mit der App-Start-Bibliothek von Jetpack können Sie die Initialisierung von Komponenten beim Anwendungsstart optimieren.

UI-Leistung analysieren

Beim Start der App werden ein Ladebildschirm und die Ladezeit Ihrer Startseite angezeigt. Zur Optimierung des App-Starts sollten Sie Traces untersuchen, um die Zeit zu ermitteln, die für das Erstellen der UI benötigt wird.

Arbeit bei der Initialisierung einschränken

Bestimmte Frames benötigen möglicherweise mehr Zeit zum Laden als andere. Dies gilt als teuer für die App.

So optimieren Sie die Initialisierung:

  • Priorisieren Sie langsame Layout-Durchläufe und nehmen Sie diese zur Verbesserung vor.
  • Prüfen Sie jede Warnung von Perfetto und Benachrichtigungen von Systrace. Fügen Sie dazu benutzerdefinierte Trace-Ereignisse hinzu, um teure Auslastungen und Verzögerungen zu reduzieren.

Frame-Daten messen

Es gibt mehrere Möglichkeiten, Frame-Daten zu messen. Die fünf wichtigsten Sammlungsmethoden sind:

  • Lokale Erfassung mit dumpsys gfxinfo:Nicht alle in den Dumpsys-Daten beobachteten Frames sind für das langsame Rendering der App oder negativ auf die Endnutzer verantwortlich. Dies ist jedoch eine gute Messung über verschiedene Release-Zyklen hinweg, um den allgemeinen Leistungstrend zu verstehen. Weitere Informationen zur Verwendung von gfxinfo und framestats, um UI-Leistungsmessungen in Ihre Testpraktiken zu integrieren, finden Sie unter Grundlagen des Testens von Android-Apps.
  • Felderfassung mit JankStats:Erfassen Sie Frame-Renderingzeiten bestimmter Teile Ihrer App mit der JankStats-Bibliothek und erfassen und analysieren Sie die Daten.
  • In Tests mit MacroBenchmark (perfetto im Hintergrund)
  • Perfetto-FrameTimeline:Unter Android 12 (API-Level 31) können Sie Messwerte für die Frame-Zeitachse aus einem Perfetto-Trace erfassen, in dem der Frame verloren geht. Dies kann der erste Schritt sein, um zu diagnostizieren, warum Frames ausgelassen werden.
  • Android Studio Profiler für die Verzögerungserkennung

Ladezeit der Hauptaktivität prüfen

Die Hauptaktivität Ihrer Anwendung kann eine große Menge an Informationen enthalten, die aus mehreren Quellen geladen werden. Prüfen Sie das Layout „Activity“ des Zuhauses und insbesondere die Methode Choreographer.onDraw der Aktivität für Ihr Zuhause.

  • Verwenden Sie reportFullyDrawn, um dem System zu melden, dass Ihre App jetzt vollständig zur Optimierung fertiggestellt ist.
  • Messen Sie Aktivitäten und App-Starts mit StartupTimingMetric und der MacroBenchmark-Bibliothek.
  • Sieh dir Frame-Drops an.
  • Ermitteln Sie Layouts, deren Rendering oder Messung lange dauert.
  • Erkennen Sie Assets, deren Ladevorgang lange dauert.
  • Identifizieren Sie unnötige Layouts, die beim Start überhöht werden.

Erwägen Sie diese möglichen Lösungen, um die Ladezeit der Hauptaktivität zu optimieren:

  • Gestalten Sie Ihr anfängliches Layout so einfach wie möglich. Weitere Informationen finden Sie unter Layouthierarchien optimieren.
  • Fügen Sie benutzerdefinierte Tracepoints hinzu, um weitere Informationen zu entfernten Frames und komplexen Layouts bereitzustellen.
  • Minimieren Sie die Anzahl und Größe der Bitmapressourcen, die beim Start geladen werden.
  • Verwenden Sie ViewStub, wenn Layouts nicht sofort auf VISIBLE gesetzt sind. Eine ViewStub ist eine unsichtbare Ansicht mit Nullgröße, die verwendet werden kann, um Layoutressourcen während der Laufzeit verzögert aufzublähen. Weitere Informationen finden Sie unter ViewStub.

    Wenn Sie Jetpack Compose verwenden, können Sie ein ähnliches Verhalten erzielen wie ViewStub mithilfe des Status, um das Laden einiger Komponenten zu verzögern:

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    Laden Sie die zusammensetzbaren Funktionen in den bedingten Block, indem Sie shouldLoad ändern:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Dies löst eine Neuzusammensetzung aus, bei der der Code im bedingten Block im ersten Snippet enthalten ist.