Analiza i optymalizacja uruchamiania aplikacji

Podczas uruchamiania aplikacja robi pierwsze wrażenie na użytkownikach. Aplikacja musi szybko się wczytywać i wyświetlać informacje potrzebne użytkownikowi. Jeśli uruchomienie aplikacji trwa zbyt długo, użytkownicy mogą ją zamknąć, bo czekają zbyt długo.

Do pomiaru uruchomienia zalecamy użycie biblioteki testów porównawczych makr. Biblioteka zawiera przegląd i szczegółowe ślady systemu, dzięki którym dokładnie sprawdzisz, co dzieje się podczas uruchamiania.

Ślady systemu dostarczają przydatnych informacji o tym, co dzieje się na urządzeniu, dzięki czemu możesz się dowiedzieć, co robi aplikacja podczas uruchamiania, i określić obszary wymagające optymalizacji.

Aby przeanalizować uruchamianie aplikacji:

Etapy analizy i optymalizacji uruchamiania

Podczas uruchamiania aplikacje często muszą wczytywać określone zasoby, które są kluczowe dla użytkowników. Nieistotne zasoby mogą czekać na załadowanie, aż się zakończy.

Aby obniżyć skuteczność, weź pod uwagę te kwestie:

  • Korzystaj z biblioteki Makroporównań, aby mierzyć czas potrzebny na każdą operację i identyfikować bloki, których wykonanie zajmuje dużo czasu.

  • Sprawdź, czy operacja wymagająca znacznych zasobów ma kluczowe znaczenie dla uruchamiania aplikacji. Jeśli operacja może czekać, aż aplikacja zostanie w pełni pobrana, może pomóc zminimalizować ograniczenia zasobów podczas uruchamiania.

  • Sprawdź, czy ta operacja ma być uruchamiana podczas uruchamiania aplikacji. Często niepotrzebne operacje są wywoływane ze starszego kodu lub z bibliotek zewnętrznych.

  • Jeśli to możliwe, przenieś długo trwające operacje do tła. Procesy w tle mogą nadal wpływać na wykorzystanie procesora podczas uruchamiania.

Po dokładnym zbadaniu operacji możesz zdecydować, jaki jest kompromis między czasem wczytywania a koniecznością uwzględniania jej przy uruchamianiu aplikacji. Pamiętaj, aby przy zmianie przepływu pracy aplikacji uwzględnić ryzyko regresji lub zmian powodujących niezgodność.

Przeprowadź optymalizację i ponownie mierz, aż czas uruchamiania aplikacji będzie zadowalający. Więcej informacji znajdziesz w artykule Korzystanie ze wskaźników do wykrywania i diagnozowania problemów.

Mierz i analizuj czas spędzony na ważnych operacjach

Po utworzeniu pełnego logu czasu uruchamiania aplikacji sprawdź go i zmierz czas w przypadku głównych operacji, takich jak bindApplication lub activityStart. Do analizowania tych logów czasu zalecamy korzystanie z Perfetto lub programu profilującego Android Studio.

Przeanalizuj ogólny czas uruchamiania aplikacji, aby zidentyfikować operacje, które:

  • Zajmują duże przedziały czasowe i można je optymalizować. Liczy się każda milisekunda dla wydajności. Poszukaj na przykład czasów rysowania Choreographer, czasów inflacji układu, czasów wczytywania biblioteki, transakcji Binder lub czasów wczytywania zasobów. Na początek przejrzyj wszystkie operacje, które trwają dłużej niż 20 ms.
  • Zablokuj wątek główny. Więcej informacji znajdziesz w artykule Poruszanie się po raporcie Systrace.
  • Nie muszą uruchamiać się podczas uruchamiania.
  • Może zaczekać na zakończenie rysowania pierwszej klatki.

Przeanalizuj dokładnie każdy z tych logów czasu, aby znaleźć luki w wydajności.

Identyfikowanie kosztownych operacji w wątku głównym

Sprawdzoną metodą jest pozostawianie kosztownych operacji, takich jak wejście/wyjście plików i dostęp do sieci, z wątku głównego. Jest to równie ważne przy uruchamianiu aplikacji, ponieważ kosztowne operacje w wątku głównym mogą spowodować, że aplikacja przestanie reagować i opóźnić inne operacje o znaczeniu krytycznym. StrictMode.ThreadPolicy może pomóc w wykrywaniu przypadków, w których w wątku głównym są wykonywane kosztowne operacje. Dobrze jest włączyć StrictMode w kompilacjach do debugowania, aby jak najszybciej rozpoznawać problemy. Jak pokazujemy w tym przykładzie:

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()
            );
        }
        ...
    }
}

Użycie zasady StrictMode.ThreadPolicy powoduje włączenie zasady dotyczącej wątków we wszystkich kompilacjach do debugowania. Po wykryciu naruszenia tej zasady aplikacja powoduje awarie, co utrudnia przeoczenie tych zasad.

TTID i TTFD

Aby sprawdzić czas potrzebny aplikacji na wytworzenie pierwszej klatki, zmierz czas do początkowego wyświetlenia (TTID). Te dane niekoniecznie jednak odzwierciedlają czas, po którym użytkownik może zacząć korzystać z Twojej aplikacji. Czas do pełnego wyświetlenia (TTFD) jest bardziej przydatny przy mierzeniu i optymalizowaniu ścieżek kodu niezbędnych do uzyskania w pełni przydatnego stanu aplikacji.

Strategie dotyczące raportowania, gdy interfejs aplikacji jest w pełni wyświetlony, znajdziesz w artykule Zwiększanie dokładności czasu uruchamiania.

Optymalizuj kampanię pod kątem TTID i TFD, ponieważ obie te usługi są ważne we własnych obszarach. Krótki tekst TTID pokazuje użytkownikowi, że aplikacja rzeczywiście się uruchamia. Ważne jest, aby tekst TTFD był krótki, ponieważ dzięki temu użytkownik mógł szybko zacząć korzystać z aplikacji.

Analizowanie ogólnego stanu wątku

Wybierz czas uruchamiania aplikacji i przyjrzyj się ogólnym wycinkom wątków. Wątek główny musi być stale reagowany.

Narzędzia takie jak Android Studio Profiler i Perfetto zapewniają szczegółowe informacje o wątku głównym i czasie spędzonym na poszczególnych etapach. Więcej informacji o wizualizowaniu logów czasu perfetto znajdziesz w dokumentacji interfejsu Perfetto.

Zidentyfikuj główne fragmenty stanu uśpienia głównego wątku

Jeśli śpisz długo, prawdopodobnie jest to spowodowane oczekiwaniem na zakończenie pracy w głównym wątku aplikacji. Jeśli masz aplikację wielowątkową, znajdź wątek, na który czeka Twój wątek główny, i rozważ zoptymalizowanie tych operacji. Warto też sprawdzić, czy nie występują niepotrzebne rywalizacje o blokadę, które mogłyby powodować opóźnienia na ścieżce krytycznej.

Ogranicz blokowanie wątków głównych i nieprzerwany sen

Poszukaj każdego wystąpienia wątku głównego w stanie zablokowania. Perfetto i Studio Profiler pokazują to z pomarańczowym wskaźnikiem na osi czasu stanu wątku. Określ operacje, sprawdź, czy są one oczekiwane i czy można ich uniknąć, i w razie potrzeby dokonaj optymalizacji.

Przerywany sen związany z IO może być świetną okazją do poprawy. Inne procesy wykonujące operacje wejścia-wyjścia, nawet jeśli nie są ze sobą powiązanymi aplikacjami, mogą rywalizować z tym, co robi najpopularniejsza aplikacja.

Skrócenie czasu uruchamiania

Gdy znajdziesz możliwość optymalizacji, zapoznaj się z możliwymi rozwiązaniami, które pomogą skrócić czas uruchamiania:

  • Ładuj treści leniwie i asynchronicznie, aby przyspieszyć działanie TTID.
  • Zminimalizuj funkcje, które wykonują wywołania Binder. Jeśli są one nie do uniknięcia, optymalizuj te wywołania przez zapisywanie wartości w pamięci podręcznej zamiast ich powtarzania lub przenoszenie nieblokujących zadań do wątków w tle.
  • Aby przyspieszyć uruchamianie aplikacji, możesz jak najszybciej wyświetlić użytkownikowi wersję wymagającą minimalnego wyrenderowania, dopóki pozostała część ekranu nie zostanie wczytana.
  • Utwórz profil startowy i dodaj go do aplikacji.
  • Użyj biblioteki uruchamiania aplikacji Jetpack, aby uprościć inicjowanie komponentów podczas uruchamiania aplikacji.

Analizowanie wydajności interfejsu użytkownika

Uruchamianie aplikacji obejmuje ekran powitalny i czas wczytywania strony głównej. Aby zoptymalizować uruchamianie aplikacji, przeanalizuj logi czasu i sprawdź, ile czasu zajmuje wygenerowanie interfejsu użytkownika.

Ogranicz pracę przy inicjowaniu

Wczytywanie niektórych klatek może trwać dłużej niż innych. Są one uważane za kosztowne.

Aby zoptymalizować inicjowanie:

  • Nadawaj priorytety powolnym układom i wybieraj te elementy do poprawy.
  • Przeanalizuj każde ostrzeżenie z Perfetto i alert z Systrace, dodając niestandardowe zdarzenia logu czasu, aby ograniczyć kosztowne pobieranie i opóźnienia.

Pomiar danych ramki

Dane ramki można mierzyć na kilka sposobów. Oto 5 głównych metod zbierania danych:

  • Zbieranie lokalne przy użyciu dumpsys gfxinfo: nie wszystkie klatki zaobserwowane w danych dumpsys są odpowiedzialne za powolne renderowanie aplikacji lub nie mają żadnego wpływu na użytkowników. Jest to jednak dobry wskaźnik, który należy wziąć pod uwagę w różnych cyklach premierowych, aby zrozumieć ogólny trend w zakresie wydajności. Więcej informacji o używaniu metod gfxinfo i framestats do integracji pomiarów wydajności interfejsu z metodami testowania znajdziesz w artykule Podstawy testowania aplikacji na Androida.
  • Zbieranie pól za pomocą JankStats: za pomocą biblioteki JankStats możesz rejestrować czasy renderowania klatek z określonych części aplikacji oraz rejestrować i analizować dane.
  • Testy z wykorzystaniem Macroporównania (Perfetto)
  • PerfettoFrameTimeline: na Androidzie 12 (poziom interfejsu API 31) możesz zbierać dane osi czasu ramki ze śledzenia Perfetto, do którego pracy powoduje spadek klatki. Może to być pierwszy krok do ustalenia, dlaczego klatki są pomijane.
  • Program profilujący w Android Studio do wykrywania zacięć

Sprawdzanie czasu wczytywania głównej aktywności

Główna aktywność w aplikacji może zawierać duże ilości informacji wczytywanych z różnych źródeł. Sprawdź układ Activity domu, a zwłaszcza metodę Choreographer.onDraw związaną z aktywnością domową.

  • Aby zgłosić do systemu, że Twoja aplikacja jest już w pełni pobrana do celów optymalizacji, użyj narzędzia reportFullyDrawn.
  • Pomiar aktywności i uruchamiania aplikacji za pomocą StartupTimingMetric i biblioteki Macroporównanie.
  • Spójrz na spadki klatek.
  • Zidentyfikuj układy, których renderowanie lub pomiar trwa długo.
  • Identyfikuj zasoby, które długo się ładują.
  • Zidentyfikuj niepotrzebne układy, które są nadmuchane podczas uruchamiania.

Rozważ te możliwe rozwiązania, aby zoptymalizować czas wczytywania głównej aktywności:

  • Postaraj się, aby początkowy układ był jak najbardziej podstawowy. Więcej informacji znajdziesz w artykule o optymalizowaniu hierarchii układu.
  • Dodaj niestandardowe punkty śledzenia, aby dostarczyć więcej informacji o porzuconych klatkach i złożonych układach.
  • Zmniejsz liczbę i rozmiar zasobów map bitowych wczytywanych podczas uruchamiania.
  • Używaj ViewStub tam, gdzie układy nie mają od razu wartości VISIBLE. ViewStub to niewidoczny widok o zerowym rozmiarze, który umożliwia leniwe zwiększanie zasobów układu w czasie działania. Więcej informacji: ViewStub.

    Jeśli korzystasz z Jetpack Compose, możesz uzyskać działanie podobne do ViewStub w przypadku użycia stanu, aby opóźnić wczytywanie niektórych komponentów:

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

    Wczytaj elementy kompozycyjne wewnątrz bloku warunkowego, modyfikując shouldLoad:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Powoduje to zmianę kompozycji polegającej na umieszczaniu kodu wewnątrz bloku warunkowego w pierwszym fragmencie.