Czas uruchomienia aplikacji

Użytkownicy oczekują, że aplikacje będą się szybko wczytywać i będą działać bez opóźnień. Aplikacja, która uruchamia się powoli, nie spełnia tych oczekiwań i może zniechęcić użytkowników. Takie problemy mogą spowodować, że użytkownik wystawi Twojej aplikacji niską ocenę w Sklepie Play lub całkowicie z niej zrezygnuje.

Na tej stronie znajdziesz informacje, które pomogą Ci zoptymalizować czas uruchamiania aplikacji. Dowiesz się m.in., jak przebiega proces uruchamiania, jak profilować wydajność uruchamiania oraz jakie są typowe problemy z czasem uruchamiania i jak je rozwiązywać.

Informacje o różnych stanach uruchamiania aplikacji

Uruchomienie aplikacji może nastąpić w jednym z 3 stanów: uruchomienie „na zimno”, uruchamianie częściowo z pamięci lub uruchamianie z pamięci. Każdy stan wpływa na to, jak długo użytkownik będzie musiał czekać na wyświetlenie aplikacji. W przypadku uruchomienia „na zimno” aplikacja jest uruchamiana od zera. W pozostałych stanach system musi przenieść działającą aplikację z tła na pierwszy plan.

Zalecamy, aby zawsze przeprowadzać optymalizację na podstawie założenia o uruchomieniu „na zimno”. Może to też poprawić wydajność ciepłych i gorących startów.

Aby zoptymalizować aplikację pod kątem szybkiego uruchamiania, warto zrozumieć, co dzieje się na poziomie systemu i aplikacji oraz jak te poziomy wchodzą ze sobą w interakcje w każdym z tych stanów.

Dwa ważne wskaźniki określające uruchamianie aplikacji to czas do początkowego wyświetlania (TTID)czas do pełnego wyświetlenia (TTFD). TTID to czas potrzebny do wyświetlenia pierwszej klatki, a TTFD to czas potrzebny do pełnego uruchomienia aplikacji. Obie te wartości są równie ważne, ponieważ TTID informuje użytkownika, że aplikacja się wczytuje, a TTFD to moment, w którym aplikacja jest już użyteczna. Jeśli któryś z tych czasów jest zbyt długi, użytkownik może zamknąć aplikację, zanim się ona w pełni załaduje.

Uruchomienie „na zimno”

Uruchomienie „na zimno” oznacza uruchomienie aplikacji od zera. Oznacza to, że do tego momentu proces systemowy tworzy proces aplikacji. Uruchomienie „na zimno” następuje na przykład wtedy, gdy aplikacja jest uruchamiana po raz pierwszy od momentu uruchomienia urządzenia lub od momentu, gdy system ją zamknął.

Ten typ stanowi największe wyzwanie pod względem skrócenia czasu uruchomienia, bo zarówno system, jak i aplikacja muszą przeprowadzić więcej procesów niż w przypadku innych stanów uruchomienia.

Na początku uruchomienia „na zimno” system wykonuje te 3 zadania:

  1. Załaduj i uruchom aplikację.
  2. Wyświetlaj puste okno początkowe aplikacji natychmiast po jej uruchomieniu.
  3. Utwórz proces aplikacji.

Gdy system utworzy proces aplikacji, będzie on odpowiedzialny za kolejne etapy:

  1. Utwórz obiekt aplikacji.
  2. Uruchom główny wątek.
  3. Utwórz główną aktywność.
  4. zwiększać liczbę wyświetleń,
  5. Rozmieść elementy na ekranie.
  6. Przeprowadź losowanie początkowe.

Gdy proces aplikacji zakończy pierwsze rysowanie, proces systemowy zamieni wyświetlane okno tła, zastępując je głównym działaniem. W tym momencie użytkownik może zacząć korzystać z aplikacji.

Rysunek 1 pokazuje, jak procesy systemowe i aplikacji przekazują sobie pracę.

Rysunek 1. Wizualne przedstawienie ważnych elementów zimnego uruchomienia aplikacji.

Problemy z wydajnością mogą wystąpić podczas tworzenia aplikacji i aktywności.

Tworzenie aplikacji

Po uruchomieniu aplikacji puste okno początkowe pozostaje na ekranie, dopóki system nie zakończy pierwszego rysowania aplikacji. W tym momencie proces systemowy zamienia okno początkowe na okno aplikacji, umożliwiając użytkownikowi korzystanie z niej.

Jeśli zastąpisz metodę Application.onCreate() we własnej aplikacji, system wywoła metodę onCreate() w obiekcie aplikacji. Następnie aplikacja tworzy wątek główny, zwany też wątkiem UI, i przypisuje mu zadanie utworzenia głównej aktywności.

Od tego momentu procesy na poziomie systemu i aplikacji przebiegają zgodnie z etapami cyklu życia aplikacji.

Tworzenie aktywności

Po utworzeniu aktywności przez proces aplikacji wykonuje ona te operacje:

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

Metoda onCreate() zwykle ma największy wpływ na czas wczytywania, ponieważ wykonuje pracę z największym narzutem: ładowanie i rozszerzanie widoków oraz inicjowanie obiektów potrzebnych do działania aktywności.

Uruchamianie częściowo z pamięci

Uruchamianie częściowo z pamięci obejmuje podzbiór operacji, które mają miejsce podczas uruchomienia „na zimno”. Jednocześnie wiąże się to z większym obciążeniem niż uruchomienie z pamięci. Istnieje wiele potencjalnych stanów, które można uznać za ciepły start, np.:

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

  • System usuwa aplikację z pamięci, a użytkownik uruchamia ją ponownie. Proces i aktywność muszą zostać ponownie uruchomione, ale zadanie może w pewnym stopniu skorzystać z zapisanego pakietu stanu instancji przekazanego do onCreate().

Uruchamianie z pamięci

Uruchomienie aplikacji z pamięci ma mniejszy narzut niż uruchomienie „na zimno”. W przypadku uruchamiania z pamięci system przenosi aktywność na pierwszy plan. Jeśli wszystkie działania aplikacji nadal znajdują się w pamięci, aplikacja może uniknąć powtarzania inicjowania obiektów, rozwijania układu i renderowania.

Jeśli jednak część pamięci zostanie usunięta trwale w odpowiedzi na zdarzenia przycinania pamięci, takie jak onTrimMemory(), te obiekty muszą zostać odtworzone w odpowiedzi na zdarzenie uruchamiania z pamięci.

Uruchamianie z pamięci wygląda tak samo jak uruchamianie „na zimno”. Proces systemowy 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 zaczyna się od narysowania pierwszej klatki.

Jak zidentyfikować uruchomienie aplikacji w Perfetto

Aby rozwiązać problemy z uruchamianiem aplikacji, warto określić, co dokładnie obejmuje faza uruchamiania. Aby zidentyfikować cały etap uruchamiania aplikacji w Perfetto, wykonaj te czynności:

  1. W Perfetto znajdź wiersz z pochodną wartością Android App Startups. Jeśli nie widzisz tej opcji, spróbuj zarejestrować ślad za pomocą aplikacji do śledzenia systemu na urządzeniu.

    Ilustracja 3. Wyodrębniony fragment danych dotyczących uruchamiania aplikacji na Androida w Perfetto.
  2. Kliknij powiązany wycinek i naciśnij m, aby go wybrać. Nawiasy pojawiają się wokół wycinka i wskazują, ile czasu zajęło wykonanie danego zadania. Czas trwania jest też widoczny na karcie Bieżące zaznaczenie.

  3. Przypnij wiersz Uruchamianie aplikacji na Androida, klikając ikonę pinezki, która jest widoczna, gdy umieścisz wskaźnik myszy nad wierszem.

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

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

    Ilustracja 4. Wyodrębniony wycinek danych dotyczących uruchamiania aplikacji na Androida obok głównego wątku aplikacji.
  6. Sekcja danych pochodnych ułatwia sprawdzenie, co dokładnie jest uwzględnione w procesie uruchamiania aplikacji, dzięki czemu możesz kontynuować szczegółowe debugowanie.

Używanie danych do sprawdzania i ulepszania startupów

Aby prawidłowo zdiagnozować wydajność czasu uruchamiania, możesz śledzić dane, które pokazują, ile czasu zajmuje uruchomienie aplikacji. Android udostępnia kilka sposobów informowania o problemach z aplikacją i pomaga w ich diagnozowaniu. Android Vitals może Cię powiadomić o wystąpieniu problemu, a narzędzia diagnostyczne pomogą go zdiagnozować.

Korzyści z korzystania z danych o start-upach

Android używa danych czasu do wyświetlania pierwszej klatki (TTID)czasu do pełnego wyświetlenia (TTFD), aby optymalizować uruchamianie aplikacji „na zimno” i „na ciepło”. Środowisko wykonawcze Androida (ART) wykorzystuje dane z tych pomiarów do wydajnego wstępnego kompilowania kodu w celu optymalizacji przyszłych uruchomień.

Szybsze uruchamianie aplikacji prowadzi do bardziej trwałej interakcji użytkowników z aplikacją, co zmniejsza liczbę przypadków wczesnego zamykania, ponownego uruchamiania instancji lub przechodzenia do innej aplikacji.

Android Vitals

Android Vitals może pomóc Ci poprawić działanie aplikacji, wysyłając alert w Konsoli Play za każdym razem, gdy czas uruchamiania aplikacji jest zbyt długi.

Android Vitals uznaje za nadmierne te czasy uruchamiania aplikacji:

Android Vitals korzysta z danych czas do początkowego wyświetlania (TTID). Więcej informacji o tym, jak Google Play zbiera dane Android Vitals, znajdziesz w dokumentacji Konsoli Play.

Czas do początkowego wyświetlenia

Czas do początkowego wyświetlenia (TTID) to czas potrzebny na wyświetlenie pierwszej klatki interfejsu aplikacji. Ta wartość to czas potrzebny aplikacji na wygenerowanie pierwszej klatki, w tym inicjowanie procesu podczas uruchomienia „na zimno”, tworzenie aktywności podczas uruchomienia „na zimno” lub uruchamiania częściowo z pamięci oraz wyświetlanie pierwszej klatki. Niski TTID aplikacji poprawia wrażenia użytkowników, ponieważ pozwala im szybko uruchamiać aplikację. Identyfikator TTID jest automatycznie zgłaszany w przypadku każdej aplikacji przez platformę Android. Podczas optymalizacji pod kątem uruchamiania aplikacji zalecamy wdrożenie reportFullyDrawn, aby uzyskać informacje o TTFD.

TTID jest mierzony jako wartość czasu, która reprezentuje łączny czas, jaki upłynął od wystąpienia następujących zdarzeń:

  • Uruchamianie procesu.
  • Inicjowanie obiektów.
  • Tworzenie i inicjowanie aktywności.
  • Rozwijanie układu.
  • Pierwsze rysowanie aplikacji.

Pobieranie identyfikatora TTID

Aby znaleźć TTID, w narzędziu wiersza poleceń Logcat wyszukaj wiersz wyjściowy zawierający wartość o nazwie Displayed. Ta wartość to TTID. Wygląda ona podobnie do tego przykładu, w którym TTID wynosi 3s534ms:

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

Aby znaleźć TTID w Android Studio, wyłącz filtry w widoku Logcat w menu filtra, a następnie znajdź Displayed czas, jak pokazano na rysunku 5. Wyłączenie filtrów jest konieczne, ponieważ ten dziennik jest obsługiwany przez serwer systemowy, a nie przez samą aplikację.

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

Wartość Displayed w danych wyjściowych Logcat nie musi odzwierciedlać czasu, jaki upłynął do momentu załadowania i wyświetlenia wszystkich zasobów. Pomija zasoby, do których nie ma odwołań w pliku układu lub które aplikacja tworzy w ramach inicjowania obiektu. Nie uwzględnia tych zasobów, ponieważ ich wczytywanie jest procesem wbudowanym i nie blokuje początkowego wyświetlania aplikacji.

Czasami w wierszu Displayed w danych wyjściowych Logcat znajduje się dodatkowe pole z całkowitym czasem. Przykład:

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

W tym przypadku pomiar czasu pierwszego wyświetlenia dotyczy tylko aktywności, która jest rysowana jako pierwsza. Pomiar czasu total rozpoczyna się od uruchomienia procesu aplikacji i może obejmować inną aktywność, która jest uruchamiana jako pierwsza, ale nie wyświetla niczego na ekranie. total Pomiar czasu jest wyświetlany tylko wtedy, gdy występuje różnica między czasem uruchomienia pojedynczego działania a łącznym czasem uruchomienia.

Zalecamy używanie Logcata w Androidzie Studio, ale jeśli nie korzystasz z Androida Studio, możesz też zmierzyć TTID, uruchamiając aplikację za pomocą adbpolecenia menedżera aktywności powłoki. 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

Dane Displayed pojawią się w danych wyjściowych Logcat tak jak wcześniej. W oknie terminala wyświetli się:

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

Argumenty -c-a są opcjonalne i umożliwiają określenie wartości <category><action>.

Czas do pełnego wyświetlenia

Czas do pełnego wyświetlenia (TTFD) to czas, po którym aplikacja staje się interaktywna dla użytkownika. Jest to czas potrzebny na wyświetlenie pierwszej klatki interfejsu aplikacji, a także treści, które wczytują się asynchronicznie po wyświetleniu pierwszej klatki. Zwykle są to główne treści wczytywane z sieci lub dysku, zgodnie z informacjami podanymi przez aplikację. Innymi słowy, TTFD obejmuje TTID oraz czas potrzebny na to, aby aplikacja stała się użyteczna. Niski czas do pierwszego wyświetlenia (TTFD) w aplikacji poprawia komfort użytkowania, ponieważ pozwala użytkownikom szybko wchodzić w interakcję z aplikacją.

System określa TTID, gdy Choreographer wywołuje metodę onDraw() aktywności i 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ć TTFD, aplikacja musi wysyłać do systemu sygnał, gdy osiągnie stan pełnego wyrenderowania.

Pobieranie TTFD

Aby znaleźć TTFD, zasygnalizuj w pełni narysowany stan, wywołując metodę reportFullyDrawn() obiektu ComponentActivity. Metoda reportFullyDrawn informuje, kiedy aplikacja jest w pełni narysowana i gotowa do użycia. TTFD to czas, który upłynął od momentu otrzymania przez system intencji uruchomienia aplikacji do wywołania funkcji reportFullyDrawn(). Jeśli nie wywołasz funkcji reportFullyDrawn(), nie zostanie zgłoszona żadna wartość TTFD.

Aby zmierzyć TTFD, wywołaj reportFullyDrawn() po całkowitym narysowaniu interfejsu i wszystkich danych. Nie wywołuj funkcji reportFullyDrawn(), zanim nie zostanie narysowane i wyświetlone pierwsze okno aktywności (zgodnie z pomiarami systemu), ponieważ w takim przypadku system zgłosi czas zmierzony przez system. Inaczej mówiąc, jeśli wywołasz funkcję reportFullyDrawn(), zanim system wykryje TTID, system zgłosi zarówno TTID, jak i TTFD jako tę samą wartość, która będzie wartością TTID.

Gdy użyjesz reportFullyDrawn(), Logcat wyświetli dane wyjściowe podobne do tego przykładu, w którym TTFD wynosi 1 s 54 ms:

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

Dane wyjściowe Logcat czasami zawierają total czas, jak opisano w sekcji Czas do pierwszego wyświetlenia.

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

W podstawowych przypadkach, gdy wiesz, że stan pełnego wyrenderowania został osiągnięty, możesz użyć reportFullyDrawn(), aby to zasygnalizować. Jeśli jednak wątki w tle muszą wykonać pracę w tle, zanim zostanie osiągnięty stan pełnego rysowania, musisz opóźnić reportFullyDrawn(), aby uzyskać dokładniejszy pomiar TTFD. Aby dowiedzieć się, jak opóźnić reportFullyDrawn(), zapoznaj się z następną sekcją.

Poprawianie dokładności czasu uruchamiania

Jeśli Twoja aplikacja wykonuje leniwe ładowanie, a początkowe wyświetlanie nie obejmuje wszystkich zasobów, np. gdy aplikacja pobiera obrazy z sieci, możesz opóźnić wywołanie funkcji reportFullyDrawn do momentu, gdy aplikacja stanie się użyteczna. Dzięki temu możesz uwzględnić wypełnianie listy w czasie pomiaru wydajności.

Jeśli na przykład interfejs zawiera dynamiczną listę, np. RecyclerView lub listę ładowaną na żądanie, może ona być wypełniana przez zadanie w tle, które kończy się po pierwszym narysowaniu listy, a więc po oznaczeniu interfejsu jako w pełni narysowanego. W takich przypadkach zapełnianie listy nie jest uwzględniane w analizie porównawczej.

Aby uwzględnić zapełnianie listy w czasie testu porównawczego, uzyskaj FullyDrawnReporter za pomocą getFullyDrawnReporter() i dodaj do niego w kodzie aplikacji moduł raportujący. Zwolnij reportera po zakończeniu wypełniania listy przez zadanie w tle.

FullyDrawnReporter nie wywołuje metody reportFullyDrawn(), dopóki nie zostaną zwolnione wszystkie dodane obiekty raportujące. Dodanie reportera do momentu zakończenia procesu w tle powoduje, że dane o czasie uruchamiania obejmują też czas potrzebny na wypełnienie listy. Nie zmienia to działania aplikacji w przypadku użytkownika, ale pozwala uwzględnić w danych o czasie uruchamiania czas potrzebny na wypełnienie listy. reportFullyDrawn() jest wywoływana dopiero po zakończeniu wszystkich zadań, niezależnie od kolejności.

Poniższy przykład pokazuje, jak można uruchomić jednocześnie wiele zadań w tle, z których każde rejestruje własny moduł raportujący:

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 {
                fullyDrawnReporter.addReporter()
                lifecycleScope.launch(Dispatchers.IO) {
                    // Perform the background operation.
                    fullyDrawnReporter.removeReporter()
                }
                fullyDrawnReporter.addReporter()
                lifecycleScope.launch(Dispatchers.IO) {
                    // 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 korzysta z Jetpack Compose, możesz użyć tych interfejsów API, aby wskazać stan pełnego wyrenderowania:

  • ReportDrawn: oznacza, że komponent jest od razu gotowy do interakcji.
  • ReportDrawnWhen: przyjmuje predykat, np. list.count > 0, aby wskazać, kiedy komponent jest gotowy do interakcji.
  • ReportDrawnAfter: przyjmuje metodę zawieszania, która po zakończeniu wskazuje, że komponent jest gotowy do interakcji.
Identyfikowanie wąskich gardeł

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

Możesz też uzyskać wgląd w potencjalne wąskie gardła dzięki śledzeniu wbudowanemu w onCreate()metody aplikacji i aktywności. Więcej informacji o śledzeniu wbudowanym znajdziesz w dokumentacji funkcji Trace i w omówieniu śledzenia systemu.

Rozwiązywanie typowych problemów

W tej sekcji omówimy 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.

Intensywna inicjalizacja aplikacji

Skuteczność uruchamiania może się pogorszyć, jeśli kod zastępuje obiekt Application i wykonuje złożone operacje lub logikę podczas inicjowania tego obiektu. Aplikacja może tracić czas podczas uruchamiania, jeśli podklasy Application wykonują inicjalizacje, które nie są jeszcze potrzebne.

Niektóre inicjalizacje mogą być całkowicie niepotrzebne, np. inicjalizacja informacji o stanie głównej aktywności, gdy aplikacja jest uruchamiana w odpowiedzi na intencję. W przypadku intencji aplikacja używa tylko podzbioru wcześniej zainicjowanych danych o stanie.

Inne problemy podczas inicjowania aplikacji to m.in. zdarzenia czyszczenia pamięci, które mają duży wpływ na wydajność lub występują w dużej liczbie, oraz operacje wejścia/wyjścia na dysku wykonywane równolegle z inicjowaniem, co dodatkowo blokuje proces inicjowania. Czyszczenie pamięci jest szczególnie ważne w przypadku środowiska wykonawczego Dalvik. Środowisko wykonawcze Androida (ART) wykonuje czyszczenie pamięci równolegle, co minimalizuje wpływ tej operacji.

Zdiagnozuj problem

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

Śledzenie metod

Uruchomienie CPU Profilera ujawnia, że metoda callApplicationOnCreate() ostatecznie wywołuje metodę com.example.customApplication.onCreate. Jeśli narzędzie pokazuje, że wykonanie tych metod zajmuje dużo czasu, sprawdź, jakie działania są w ich ramach wykonywane.

Śledzenie w tekście

Używaj śledzenia wbudowanego, aby zbadać prawdopodobne przyczyny problemu, w tym:

  • Początkowa funkcja onCreate() aplikacji.
  • Wszystkie globalne obiekty singleton, które inicjuje Twoja aplikacja.
  • wszelkie operacje we/wy na dyskach, deserializację lub pętle, które mogą występować w miejscu wąskiego gardła.

Rozwiązania problemu

Niezależnie od tego, czy problem dotyczy niepotrzebnych inicjalizacji, czy operacji wejścia/wyjścia na dysku, rozwiązaniem jest leniwa inicjalizacja. Innymi słowy, inicjuj tylko obiekty, które są od razu potrzebne. Zamiast tworzyć globalne obiekty statyczne, przejdź na wzorzec singleton, w którym aplikacja inicjuje obiekty tylko wtedy, gdy są one potrzebne po raz pierwszy.

Możesz też użyć platformy wstrzykiwania zależności, takiej jak Hilt, która tworzy obiekty i zależności, gdy są one wstrzykiwane po raz pierwszy.

Jeśli Twoja aplikacja używa dostawców treści do inicjowania komponentów aplikacji podczas uruchamiania, rozważ użycie biblioteki uruchamiania aplikacji.

Inicjowanie intensywnej aktywności

Tworzenie aktywności często wiąże się z dużymi kosztami. Często istnieją możliwości optymalizacji tej pracy w celu poprawy wydajności. Do takich częstych problemów należą:

  • Tworzenie dużych lub złożonych układów.
  • blokowanie rysowania ekranu na dysku lub operacji wejścia/wyjścia sieciowego;
  • Wczytywanie i dekodowanie bitmap.
  • Rasteryzacja obiektów VectorDrawable.
  • Inicjowanie innych podsystemów aktywności.

Zdiagnozuj problem

W tym przypadku przydatne mogą być zarówno śledzenie metod, jak i śledzenie wbudowane.

Śledzenie metod

Podczas korzystania z CPU Profilera zwróć uwagę na konstruktory podklas Application i metody com.example.customApplication.onCreate() w aplikacji.

Jeśli narzędzie pokazuje, że wykonanie tych metod zajmuje dużo czasu, sprawdź, jakie działania są w nich wykonywane.

Śledzenie w tekście

Używaj śledzenia wbudowanego, aby zbadać prawdopodobne przyczyny problemu, w tym:

  • Początkowa funkcja onCreate() aplikacji.
  • Wszystkie globalne obiekty singleton, które inicjuje.
  • wszelkie operacje we/wy na dyskach, deserializację lub pętle, które mogą występować w miejscu wąskiego gardła.

Rozwiązania problemu

Może wystąpić wiele wąskich gardeł, ale 2 częste problemy i ich rozwiązania to:

  • Im większa jest hierarchia widoków, tym więcej czasu zajmuje aplikacji jej rozszerzenie. Aby rozwiązać ten problem, możesz wykonać te 2 czynności:
    • Spłaszcz hierarchię widoków, zmniejszając liczbę zbędnych lub zagnieżdżonych układów.
    • Nie powiększaj części interfejsu, które nie muszą być widoczne podczas uruchamiania. Zamiast tego użyj obiektu ViewStub jako symbolu zastępczego dla podrzędnych hierarchii, które aplikacja może rozwinąć w odpowiedniejszym momencie.
  • Inicjowanie wszystkich zasobów w wątku głównym może również spowolnić uruchamianie. Aby rozwiązać ten problem:
    • Przenieś całą inicjalizację zasobów, aby aplikacja mogła wykonywać ją w sposób odroczony w innym wątku.
    • Pozwól aplikacji wczytać i wyświetlić widoki, a potem zaktualizuj właściwości wizualne zależne od map bitowych i innych zasobów.

Niestandardowe ekrany powitalne

Jeśli w Androidzie 11 (API na poziomie 30) lub starszym wdrożono niestandardowy ekran powitalny za pomocą jednej z tych metod, czas uruchamiania może być dłuższy:

  • Używanie atrybutu motywu windowDisablePreview, aby wyłączyć początkowy pusty ekran rysowany przez system podczas uruchamiania.
  • Użyj specjalnego elementu Activity.

Od Androida 12 wymagana jest migracja do interfejsu SplashScreen API. Ten interfejs API umożliwia szybsze uruchamianie i dostosowywanie ekranu powitalnego w następujący sposób:

Biblioteka zgodności przenosi też SplashScreen interfejs API do starszych wersji, aby zapewnić zgodność wsteczną i spójny wygląd ekranu powitalnego we wszystkich wersjach Androida.

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