Czas uruchomienia aplikacji

Użytkownicy oczekują, że aplikacje będą szybko się wczytywać i elastycznie. Aplikacja, która uruchamia się powoli, nie spełnia tego oczekiwań i może rozczarować użytkowników. Takie niewłaściwe wrażenia mogą spowodować, że użytkownicy słabo ocenią Twoją aplikację w Sklepie Play, a nawet całkowicie ją porzucą.

Ta strona zawiera informacje ułatwiające optymalizację czasu uruchomienia aplikacji, w tym omówienie procesu wprowadzania na rynek, profilowanie wydajności przy uruchamianiu oraz wskazówki dotyczące rozwiązywania problemów podczas uruchamiania aplikacji.

Omówienie różnych stanów uruchamiania aplikacji

Aplikacja może zostać uruchomiona w jednym z 3 stanów: „na zimno”, „na ciepło” lub z pamięci. Każdy stan wpływa na to, po jakim czasie aplikacja stanie się widoczna dla użytkownika. Podczas zimnego startu aplikacja zaczyna się od zera. W pozostałych stanach system musi przenieść uruchomioną aplikację z tle na pierwszy plan.

Zalecamy optymalizowanie kampanii zawsze na podstawie założenia „na zimno”. Może to także poprawić wydajność uruchamiania częściowo z pamięci i z pamięci.

Aby zoptymalizować aplikację pod kątem szybkiego uruchamiania, warto wiedzieć, co dzieje się na poziomach systemu i aplikacji oraz jak są one używane w każdym z tych stanów.

Dwa ważne wskaźniki służące do określania czasu uruchamiania aplikacji to czas do początkowego wyświetlenia (TTID) i czas do pełnego pobrania (TTFD). TTID to czas potrzebny na wyświetlenie pierwszej klatki, a TFD – czas potrzebny na pełną interaktywność aplikacji. Obydwie są równie ważne, ponieważ TTID informuje użytkownika, że aplikacja się wczytuje, a TFD – gdy aplikacja rzeczywiście jest używana. Jeśli któreś z tych znaków są za długie, użytkownik może zamknąć aplikację, zanim się załaduje.

Uruchomienie „na zimno”

Uruchomienie „na zimno” oznacza uruchomienie aplikacji od zera. Oznacza to, że do czasu jego uruchomienia proces aplikacji tworzy proces systemu. Uruchamianie „na zimno” ma miejsce np. w przypadku uruchomienia aplikacji po raz pierwszy od momentu uruchomienia urządzenia lub po jej zamknięciu przez system.

Ten typ uruchamiania stanowi największe wyzwanie dla skrócenia czasu uruchamiania, ponieważ system i aplikacja mają do wykonania więcej pracy niż w innych stanach uruchamiania.

Na początku uruchomienia „na zimno” system ma 3 następujące zadania:

  1. Wczytaj i uruchom aplikację.
  2. Bezpośrednio po uruchomieniu wyświetlaj puste okno początkowe aplikacji.
  3. Utwórz proces aplikacji.

Gdy tylko system utworzy proces aplikacji, jest on odpowiedzialny za kolejne etapy:

  1. Utwórz obiekt aplikacji.
  2. Uruchom wątek główny.
  3. Utwórz główne działanie.
  4. Zwiększanie atrakcyjności widoków
  5. Określ układ ekranu.
  6. Przeprowadź wstępne rysowanie.

Po zakończeniu pierwszego rysowania proces systemu zastępuje wyświetlane okno w tle i zastępuje je główną aktywnością. Teraz użytkownik może zacząć korzystać z aplikacji.

Rysunek 1 przedstawia zależność między procesami systemu i aplikacji.

Rysunek 1. Wizualna reprezentacja ważnych części uruchamiania aplikacji „na zimno”.

Podczas tworzenia aplikacji i aktywności mogą wystąpić problemy z wydajnością.

Tworzenie aplikacji

Po uruchomieniu aplikacji na ekranie pozostanie puste okno startowe, dopóki system nie zakończy rysowania po raz pierwszy. W tym momencie proces systemowy zmienia okno początkowe aplikacji, umożliwiając użytkownikowi interakcję z aplikacją.

Jeśli zastąpisz parametr Application.onCreate() we własnej aplikacji, system wywoła metodę onCreate() w obiekcie aplikacji. Następnie aplikacja tworzy wątek główny (nazywany również wątkiem UI) i tworzy dla niego główną aktywność.

Od tego momentu procesy na poziomie systemu i aplikacji będą przebiegać zgodnie z etapami cyklu życia aplikacji.

Tworzenie aktywności

Gdy proces aplikacji utworzy aktywność, będzie ona wykonywać te operacje:

  1. Inicjuje wartości.
  2. Wywołuje konstruktory.
  3. Wywołuje metodę wywołania zwrotnego, np. Activity.onCreate(), odpowiednią do bieżącego stanu cyklu życia aktywności.

Zazwyczaj metoda onCreate() ma największy wpływ na czas wczytywania, ponieważ wykonuje pracę z największym nakładem pracy: wczytywanie i nadmuchiwanie widoków oraz inicjowanie obiektów niezbędnych do uruchomienia działania.

Uruchomienie częściowo z pamięci

Uruchamianie „na zimno” obejmuje podzbiór operacji wykonywanych w trakcie uruchomienia „na zimno”. Jednocześnie wiążą się z tym większe nakłady pracy niż uruchomienie z pamięci. Istnieje wiele potencjalnych stanów, które można uznać za uruchomienia częściowo z pamięci, na przykład:

  • Użytkownik wycofuje się z aplikacji, a potem ją uruchamia ponownie. Proces może być kontynuowany, ale aplikacja musi odtworzyć aktywność od podstaw, używając wywołania onCreate().

  • System usuwa aplikację z pamięci, a potem uruchamia ją ponownie. Proces i aktywność muszą zostać ponownie uruchomione, ale zadanie może korzystnie wpłynąć na zapisany pakiet stanu instancji przekazany do onCreate().

Uruchomienie z pamięci

Uruchomienie aplikacji z pamięci jest niższe niż w przypadku uruchomienia „na zimno”. W takim przypadku system przenosi aktywność na pierwszy plan. Jeśli wszystkie działania aplikacji wciąż znajdują się w pamięci, aplikacja może uniknąć powtarzania inicjowania obiektu, inflacji układu i renderowania.

Jeśli jednak jakaś pamięć zostanie trwale usunięta w odpowiedzi na zdarzenia związane z przycinaniem pamięci (np. onTrimMemory()), trzeba odtworzyć te obiekty w odpowiedzi na zdarzenie uruchomienia z pamięci.

Uruchomienie z pamięci działa na ekranie tak samo jak w przypadku scenariusza „na zimno”. Proces systemowy będzie wyświetlać pusty ekran, dopóki aplikacja nie zakończy renderowania aktywności.

Rysunek 2. Diagram z różnymi stanami uruchamiania i odpowiednimi procesami, każdy stan zaczynający się od pierwszej narysowanej klatki.

Jak zidentyfikować uruchamianie aplikacji w Perfetto

Aby debugować problemy z uruchamianiem aplikacji, warto dokładnie określić, co jest uwzględnione na tym etapie. Aby w Perfetto zidentyfikować całą fazę uruchamiania aplikacji, wykonaj te czynności:

  1. W narzędziu Perfetto znajdź wiersz z danymi uzyskanymi podczas uruchamiania aplikacji na Androida. Jeśli go nie widzisz, spróbuj zarejestrować ślad za pomocą aplikacji do śledzenia systemu działającego na urządzeniu.

    Rys. 3.Wycinek danych uzyskany w startupach aplikacji na Androida w Perfetto.
  2. Kliknij powiązany wycinek i naciśnij m, aby go wybrać. Wokół wycinka znajdują się nawiasy, które określają czas trwania wycinka. Czas trwania jest też wyświetlany na karcie Bieżące zaznaczenie.

  3. Przypnij wiersz uruchamiania aplikacji na Androida, klikając ikonę pinezki, która jest widoczna po przytrzymaniu wskaźnika nad wierszem.

  4. Przewiń do wiersza z odpowiednią aplikacją i kliknij pierwszą komórkę, aby go rozwinąć.

  5. Powiększ wątek główny, zwykle u góry, naciskając W (naciśnij s, a, D, aby pomniejszyć, przesunąć w lewo lub w prawo).

    Rys. 4.Wycinek danych uzyskany dzięki startupom na Androida obok głównego wątku aplikacji.
  6. Wycinek danych pochodnych ułatwia sprawdzanie, co dokładnie zawiera uruchamiana aplikacja, dzięki czemu możesz kontynuować debugowanie bardziej szczegółowo.

Sprawdzaj i ulepszaj startupy na podstawie wskaźników

Aby prawidłowo zdiagnozować czas uruchamiania, możesz śledzić wskaźniki, które pokazują, ile czasu zajmuje uruchomienie aplikacji. Android udostępnia kilka sposobów na pokazanie, że aplikacja ma problem, i pomoc w jego zdiagnozowaniu. Android Vitals może Cię powiadomić o problemie, a narzędzia diagnostyczne mogą pomóc w jego zdiagnozowaniu.

Zalety korzystania ze wskaźników dotyczących startupów

Android używa danych o czasie do początkowego wyświetlenia (TTID) i czasie do pełnego wyświetlenia (TTFD), aby zoptymalizować uruchamianie aplikacji „na zimno” i „na ciepło”. Android Runtime (ART) wykorzystuje dane z tych wskaźników, aby sprawnie wstępnie kompilować kod z myślą o optymalizacji przyszłych startupów.

Szybsze uruchamianie przekłada się na trwalsze interakcje użytkownika z aplikacją, co zmniejsza liczbę przypadków wcześniejszego wychodzenia z aplikacji, ponownego uruchamiania instancji lub przechodzenia do innej aplikacji.

Android Vitals

Android Vitals może poprawić wydajność aplikacji, wyświetlając w Konsoli Play alerty o zbyt długim czasie uruchamiania aplikacji.

Android Vitals uwzględnia te czasy uruchamiania aplikacji:

  • Uruchomienie na zimno trwa co najmniej 5 sekund.
  • Uruchomienie gorąco trwa co najmniej 2 sekundy.
  • Uruchomienie z pamięci trwa co najmniej 1,5 sekundy.

Android Vitals wykorzystuje dane czasu do początkowego wyświetlenia (TTID). Informacje o tym, jak Google Play gromadzi dane Android Vitals, znajdziesz w dokumentacji Konsoli Play.

Czas do początkowego wyświetlenia

Czas do początkowego wyświetlenia (TTID) to czas potrzebny do wyświetlenia pierwszej klatki interfejsu aplikacji. Te dane określają czas potrzebny aplikacji na utworzenie pierwszej klatki, w tym inicjowanie procesu „na zimno”, tworzenie aktywności podczas uruchamiania „na zimno” lub „na ciepło” oraz wyświetlanie pierwszej klatki. Utrzymywanie niskich wartości TTID aplikacji pomaga poprawić wygodę użytkowników, ponieważ pozwala im szybko zobaczyć, jak się ona uruchamia. Funkcja TTID jest automatycznie raportowana dla każdej aplikacji przez AndroidFramework. Jeśli optymalizujesz kampanię pod kątem startupów, zalecamy wdrożenie reportFullyDrawn, aby uzyskać informacje nawet do TTFD.

Wartość TTID jest mierzona jako wartość czasu, która reprezentuje łączny czas, który obejmuje tę sekwencję zdarzeń:

  • Uruchamiam proces.
  • Inicjuję obiekty.
  • Tworzę i inicjuję działanie.
  • Zwiększam układ.
  • Rysowanie aplikacji po raz pierwszy.

Pobieranie TTID

Aby znaleźć TTID, wyszukaj w narzędziu wiersza poleceń Logcat wiersz wyjściowy zawierający wartość Displayed. Jest to wartość TTID. Wygląda ona podobnie do poniższego przykładu, w którym TTID to 3s534 ms:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

Aby znaleźć w Android Studio identyfikatory TTID, wyłącz filtry w widoku Logcat w menu filtrów, a potem znajdź czas Displayed, jak pokazano na ilustracji 5. Wyłączenie filtrów jest konieczne, ponieważ dziennik udostępnia serwer systemu, a nie sama aplikacja.

Rysunek 5. Wyłączone filtry i wartość Displayed w logcat.

Wskaźnik Displayed w danych wyjściowych Logcat niekoniecznie rejestruje ilość czasu do momentu wczytania i wyświetlenia wszystkich zasobów. Pomija zasoby, do których nie odwołuje się plik układu lub które aplikacja tworzy w ramach inicjowania obiektu. Wyklucza te zasoby, ponieważ ich wczytywanie jest procesem wbudowanym i nie blokuje początkowego wyświetlania aplikacji.

Czasami wiersz Displayed w danych wyjściowych Logcat zawiera dodatkowe pole łącznego czasu. Na przykład:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

W tym przypadku pierwszy pomiar obejmuje tylko utworzone wcześniej działanie. Pomiar czasu w usłudze total rozpoczyna się w momencie rozpoczęcia procesu aplikacji i może obejmować inną aktywność, która została uruchomiona jako pierwsza, ale nie wyświetla nic na ekranie. Pomiar czasu total wyświetla się tylko wtedy, gdy występuje różnica między czasem pojedynczego działania a łącznym czasem uruchomienia.

Zalecamy korzystanie z Logcat w Android Studio, ale jeśli nie korzystasz z Androida Studio, możesz też zmierzyć TTID, uruchamiając aplikację za pomocą polecenia powłoki adb w menedżerze aktywności. Oto przykład:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

Wskaźnik Displayed pojawi się w danych wyjściowych Logcat tak jak wcześniej. W oknie terminala zobaczysz:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

Argumenty -c i -a są opcjonalne i pozwalają określić <category> oraz <action>.

Czas do pełnego wyświetlenia

Czas do pełnego wyświetlenia (TTFD) to czas, po którym aplikacja staje się interaktywna. Raportowany jest ten raport jako czas potrzebny na wyświetlenie pierwszej klatki interfejsu aplikacji oraz treści, która wczytuje się asynchronicznie po wyświetleniu klatki początkowej. Zasadniczo są to treści wczytywane z sieci lub dysku zgodnie z raportami aplikacji. Inaczej mówiąc, TTFD uwzględnia wartość TTID oraz czas potrzebny na korzystanie z aplikacji. Utrzymywanie niskich wartości TTFD aplikacji poprawi jej wrażenia, umożliwiając im szybką interakcję z aplikacją.

System określa TTID, gdy Choreographer wywołuje metodę onDraw() działania, a gdy wie, że wywołuje ją po raz pierwszy, System nie wie jednak, kiedy określić TTFD, ponieważ każda aplikacja działa inaczej. Aby określić technologię TTFD, aplikacja musi wysłać sygnał do systemu, gdy osiągnie pełną wersję.

Pobierz TTFD

Aby znaleźć TTFD, zasygnalizuj stan w pełni narysowany, wywołując metodę reportFullyDrawn() w ComponentActivity. Metoda reportFullyDrawn podaje, że aplikacja została w pełni narysowana i w pełni nadaje się do użycia. TTFD to czas, który upłynął od momentu, gdy system otrzymał intencję uruchomienia aplikacji do chwili wywołania funkcji reportFullyDrawn(). Jeśli nie wywołasz funkcji reportFullyDrawn(), wartość TTFD nie zostanie zarejestrowana.

Aby mierzyć TTFD, wywołaj reportFullyDrawn() po całkowitym narysowaniu interfejsu i wszystkich danych. Nie wywołuj funkcji reportFullyDrawn() przed pierwszym wyświetleniem okna pierwszej aktywności i wyświetleniem wartości zmierzonej przez system, ponieważ wtedy system raportuje czas zmierzony przez system. Inaczej mówiąc, jeśli wywołasz funkcję reportFullyDrawn(), zanim system wykryje TTID, system zgłosi obie te wartości jako tę samą wartość – jest to wartość TTID.

Gdy używasz reportFullyDrawn(), Logcat wyświetla dane wyjściowe podobne do tego w tym przykładzie, gdzie TTFD wynosi 1s54 ms:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

W danych wyjściowych Logcat czasami pojawia się total raz, co zostało omówione w sekcji Czas do początkowego wyświetlenia.

Jeśli czas wyświetlania jest dłuższy niż zakładany, możesz spróbować zidentyfikować wąskie gardła w procesie uruchamiania.

Możesz użyć reportFullyDrawn(), aby zasygnalizować stan pełnego narysowania w podstawowych przypadkach, gdy wiesz, że został osiągnięty stan w pełni narysowany. Jeśli jednak wątki w tle muszą zakończyć działanie w tle, zanim zostaną w pełni narysowane, trzeba opóźnić reportFullyDrawn(), aby uzyskać dokładniejszy pomiar TTFD. Więcej informacji o opóźnieniu reportFullyDrawn() znajdziesz w sekcji poniżej.

Popraw dokładność czasu uruchamiania

Jeśli aplikacja działa w ramach leniwego ładowania, a jej początkowy widok nie zawiera wszystkich zasobów, np. gdy aplikacja pobiera obrazy z sieci, warto opóźnić wywoływanie funkcji reportFullyDrawn do czasu, aż stanie się ona dostępna, aby uwzględnić całą listę w czasie analizy porównawczej.

Jeśli na przykład interfejs zawiera listę dynamiczną, taką jak RecyclerView lub leniwą listę, może być ona wypełniana przez zadanie w tle, które kończy się po jej pierwszym narysowaniu – czyli wtedy, gdy interfejs zostanie oznaczony jako w pełni narysowany. W takich przypadkach w analizie porównawczej nie jest brana pod uwagę ludność listy.

Aby uwzględnić wypełnianie listy w czasie przeprowadzania testów porównawczych, pobierz FullyDrawnReporter za pomocą funkcji getFullyDrawnReporter() i dodaj do niej w kodzie aplikacji funkcję raportującą. Zwolnij, gdy zadanie w tle zakończy wypełnianie listy.

FullyDrawnReporter nie wywołuje metody reportFullyDrawn(), dopóki nie zwolnieni są wszyscy dodani zgłaszający. Gdy dodasz funkcję raportującego do zakończenia procesu w tle, czasy obejmują też czas potrzebny na zapełnienie listy w danych o czasie uruchamiania. Nie zmieni to działania aplikacji dla użytkownika, ale umożliwia dane o czasie uruchamiania listy z uwzględnieniem czasu. Funkcja reportFullyDrawn() nie jest wywoływana, dopóki wszystkie zadania nie zostaną ukończone, niezależnie od kolejności.

Ten przykład pokazuje, jak można jednocześnie uruchamiać wiele zadań w tle, przy których każde z nich rejestruje własnego reportera:

Kotlin

class MainActivity : ComponentActivity() {

    sealed interface ActivityState {
        data object LOADING : ActivityState
        data object LOADED : ActivityState
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var activityState by remember {
                mutableStateOf(ActivityState.LOADING as ActivityState)
            }
            fullyDrawnReporter.addOnReportDrawnListener {
                activityState = ActivityState.LOADED
            }
            ReportFullyDrawnTheme {
                when(activityState) {
                    is ActivityState.LOADING -> {
                        // Display the loading UI.
                    }
                    is ActivityState.LOADED -> {
                        // Display the full UI.
                    }
                }
            }
            SideEffect {
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
            }
        }
    }
}

Java

public class MainActivity extends ComponentActivity {
    private FullyDrawnReporter fullyDrawnReporter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fullyDrawnReporter = getFullyDrawnReporter();
        fullyDrawnReporter.addOnReportDrawnListener(() -> {
            // Trigger the UI update.
            return Unit.INSTANCE;
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

               fullyDrawnReporter.removeReporter();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

                fullyDrawnReporter.removeReporter();
            }
        }).start();
    }
}

Jeśli Twoja aplikacja używa Jetpack Compose, możesz wskazać jego pełny stan za pomocą tych interfejsów API:

  • ReportDrawn: oznacza, że funkcja kompozycyjna jest od razu gotowa do interakcji.
  • ReportDrawnWhen: wykorzystuje predykat, np. list.count > 0, aby wskazać, kiedy funkcja kompozycyjna jest gotowa do interakcji.
  • ReportDrawnAfter: wykorzystuje metodę zawieszania, która po zakończeniu wskazuje, że funkcja kompozycyjna jest gotowa do interakcji.
Zidentyfikuj wąskie gardła

Aby znaleźć wąskie gardła, możesz użyć narzędzia do profilowania CPU w Android Studio. Więcej informacji znajdziesz w artykule o sprawdzaniu aktywności procesora za pomocą narzędzia do profilowania procesora.

Możesz też uzyskać wgląd w potencjalne wąskie gardła, korzystając ze śledzenia wbudowanego w metody onCreate() stosowane w aplikacjach i aktywności. Więcej informacji o śledzeniu w tekście znajdziesz w dokumentacji funkcji Trace i omówieniu śledzenia systemu.

Rozwiązywanie najczęstszych problemów

W tej sekcji omawiamy kilka problemów, które często wpływają na wydajność uruchamiania aplikacji. Problemy te dotyczą głównie inicjowania obiektów aplikacji i aktywności oraz wczytywania ekranów.

Inicjowanie aplikacji z dużym natężeniem

Wydajność uruchamiania może się pogorszyć, gdy Twój kod zastąpi obiekt Application i podczas inicjowania obiektu wykonuje intensywną pracę lub wykonuje złożone logiki. Jeśli podklasy Application wykonują inicjowania, których jeszcze nie trzeba robić, aplikacja może marnować czas podczas uruchamiania.

Niektóre inicjalizacje mogą być całkowicie niepotrzebne, na przykład podczas inicjowania informacji o stanie głównego działania, gdy aplikacja jest rzeczywiście uruchamiana w odpowiedzi na intencję. W przypadku intencji aplikacja używa tylko podzbioru wcześniej zainicjowanych danych o stanie.

Inne wyzwania podczas inicjowania aplikacji to m.in. zdarzenia odśmiecania pamięci, które mają duże znaczenie lub są liczne, albo operacje wejścia-wyjścia dysku odbywające się jednocześnie z inicjowaniem, co dodatkowo blokuje proces inicjowania. Zbieranie śmieci jest kwestią szczególnie w środowisku wykonawczym Dalvik. Android Runtime (ART) zbiera elementy jednocześnie, minimalizując wpływ tej operacji.

Zdiagnozowanie problemu

Aby zdiagnozować problem, możesz użyć śledzenia metod lub śledzenia wbudowanego.

Śledzenie metod

Uruchomienie programu profilującego procesora pokazuje, że metoda callApplicationOnCreate() w końcu wywołuje Twoją metodę com.example.customApplication.onCreate. Jeśli narzędzie wykazuje, że wdrażanie tych metod trwa długo, zbadaj dokładniej, aby zobaczyć, jakie efekty są tam wprowadzane.

Śledzenie w tekście

Użyj śledzenia wbudowanego, by zbadać prawdopodobne przyczyny problemu, takie jak:

  • Początkowa funkcja onCreate() aplikacji.
  • Wszystkie globalne obiekty singleton zainicjowane przez aplikację.
  • Wszelkie operacje wejścia-wyjścia dysku, deserializacja lub pętle, które mogą występować podczas wąskiego gardła.

Rozwiązania problemu

Niezależnie od tego, czy problem jest związany z niepotrzebnymi inicjacjami czy z wejściem/wyjściem dysku, rozwiązaniem jest leniwe inicjowanie. Innymi słowy, inicjuj tylko te obiekty, które są potrzebne natychmiast. Zamiast tworzyć globalne obiekty statyczne, przejdź do wzorca jednostkowego, w którym aplikacja inicjuje obiekty tylko wtedy, gdy ich potrzebuje po raz pierwszy.

Rozważ też użycie platformy wstrzykiwania zależności, takiej jak Hilt, która tworzy obiekty i zależności przy pierwszym wstrzykiwaniu.

Jeśli Twoja aplikacja korzysta z dostawców treści do inicjowania komponentów aplikacji przy jej uruchamianiu, rozważ skorzystanie z biblioteki uruchamiania aplikacji.

Inicjowanie intensywnej aktywności

Tworzenie aktywności wiąże się często z dużą pracą. Często możliwe jest zoptymalizowanie tych prac w celu poprawy skuteczności. Do typowych problemów należą:

  • Nadużywanie dużych lub złożonych układów.
  • Blokowanie rysowania ekranu na dysku lub w trybie wejścia-wyjścia sieci.
  • Wczytywanie i dekodowanie map bitowych.
  • Rasteryzacja obiektów VectorDrawable.
  • Inicjowanie innych podsystemów aktywności.

Zdiagnozowanie problemu

W tym przypadku przydatne może być zarówno śledzenie metod, jak i śledzenie w tekście.

Śledzenie metod

Podczas korzystania z programu profilującego procesora zwróć uwagę na konstruktory podklas Application i metody com.example.customApplication.onCreate() aplikacji.

Jeśli narzędzie wykazuje, że wdrażanie tych metod trwa zbyt długo, zbadaj dokładniej, aby zobaczyć, jakie efekty są tam wprowadzane.

Śledzenie w tekście

Użyj śledzenia wbudowanego, by zbadać prawdopodobne przyczyny problemu, takie jak:

  • Pierwsza funkcja aplikacji onCreate().
  • Wszelkie zainicjowane przez niego globalne obiekty singleton.
  • Wszelkie operacje wejścia-wyjścia dysku, deserializacja lub pętle, które mogą występować podczas wąskiego gardła.

Rozwiązania problemu

Istnieje wiele potencjalnych wąskich gardła, ale dwa najczęstsze problemy i sposoby ich rozwiązania to:

  • Im większa hierarchia widoków, tym więcej czasu potrzebuje aplikacja, aby ją powiększyć. Aby rozwiązać ten problem, wykonaj te czynności:
    • Spłaszcz hierarchię widoków, ograniczając nadmiarowe lub zagnieżdżone układy.
    • Nie umieszczaj w interfejsie tych elementów, które nie muszą być widoczne podczas uruchamiania. Zamiast niego użyj obiektu ViewStub jako obiektu zastępczego dla podhierarchii, które aplikacja może zwiększać w odpowiednim momencie.
  • Inicjowanie wszystkich zasobów w wątku głównym może też spowolnić uruchamianie. Możesz rozwiązać ten problem w następujący sposób:
    • Przenieś wszystkie inicjowanie zasobów, aby aplikacja mogła wykonywać je leniwie w innym wątku.
    • Poczekaj, aż aplikacja się załaduje i wyświetli widoki, a potem zaktualizuj właściwości wizualne, które są zależne od map bitowych i innych zasobów.

Niestandardowe ekrany powitalne

Podczas uruchamiania możesz zauważyć dodatkowy czas, jeśli w Androidzie 11 (poziom interfejsu API 30) lub starszym wdrożyłeś wcześniej jedną z tych metod:

  • Użyj atrybutu motywu windowDisablePreview, aby wyłączyć początkowy pusty ekran narysowany przez system podczas uruchamiania.
  • Korzystanie z dedykowanego Activity.

Począwszy od Androida 12, konieczna jest migracja do interfejsu API SplashScreen. Ten interfejs API przyspiesza uruchamianie i umożliwia dostosowanie ekranu powitalnego na te sposoby:

Ponadto biblioteka compat obsługuje interfejs SplashScreen API, aby zapewnić zgodność wsteczną oraz stworzyć spójny wygląd ekranu powitalnego we wszystkich wersjach Androida.

Szczegółowe informacje znajdziesz w przewodniku po migracji ekranu powitalnego.