Czas uruchomienia aplikacji

Użytkownicy oczekują, że aplikacje będą się wczytywać szybko i płynnie reagować na ich działania. Aplikacja, która uruchamia się powoli, nie spełnia tych oczekiwań i może rozczarować użytkowników. Takie wrażenia mogą spowodować, że użytkownik wystawi niską ocenę Twojej aplikacji w Sklepie Play lub całkowicie zrezygnuje z jej używania.

Na tej stronie znajdziesz informacje, które pomogą Ci zoptymalizować czas uruchamiania aplikacji, w tym ogólne informacje o procesie uruchamiania, wskazówki dotyczące profilowania wydajności podczas uruchamiania oraz kilka typowych problemów z czasem uruchamiania i sposoby ich rozwiązania.

Informacje o różnych stanach uruchamiania aplikacji

Aplikacja może być uruchamiana w jednym z 3 stanów: zimny, częściowo z pamięci lub w pełni z pamięci. Każdy stan wpływa na czas, po jakim aplikacja staje się widoczna dla użytkownika. W przypadku uruchomienia „na zimno” aplikacja uruchamia się od zera. W pozostałych przypadkach system musi przenieść uruchomioną aplikację z tła na pierwszy plan.

Zalecamy, aby zawsze optymalizować model na podstawie założenia, że jest to zimny start. Może to też poprawić wydajność uruchamiania z zimnego i ciepłego stanu.

Aby zoptymalizować aplikację pod kątem szybkiego uruchamiania, warto zrozumieć, co dzieje się na poziomie systemu i aplikacji oraz jak te elementy współdziałają w każdym z tych stanów.

2 ważne dane określające czas uruchamiania aplikacji to czas do początkowego wyświetlenia (TTID)czas do pełnego wyświetlenia (TTFD). TTID to czas potrzebny na wyświetlenie pierwszej klatki, a TTFD to czas, po którym aplikacja staje się w pełni interaktywna. Oba te identyfikatory są równie ważne, ponieważ TTID informuje użytkownika, że aplikacja się wczytuje, a TTFD informuje, że można już z niej korzystać. Jeśli któryś z tych elementów jest zbyt długi, użytkownik może zamknąć aplikację, zanim zostanie ona w pełni załadowana.

Uruchomienie „na zimno”

Uruchomienie „na zimno” oznacza uruchomienie aplikacji od zera. Oznacza to, że do tego momentu proces systemu tworzy proces aplikacji. Uruchomienie „na zimno” ma miejsce na przykład wtedy, gdy aplikacja uruchamia się po raz pierwszy od uruchomienia urządzenia lub od momentu jej zabicia przez system.

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 zimnego startu system musi wykonać 3 zadania:

  1. Załaduj i uruchamiaj aplikację.
  2. wyświetlać puste okno początkowe aplikacji zaraz po jej uruchomieniu;
  3. Utwórz proces aplikacji.

Gdy system utworzy proces aplikacji, proces ten odpowiada za kolejne etapy:

  1. Utwórz obiekt aplikacji.
  2. Uruchom wątek główny.
  3. Utwórz główną aktywność.
  4. Wzrost liczby wyświetleń.
  5. Układ ekranu.
  6. Wykonaj początkowy rysunek.

Gdy proces aplikacji zakończy pierwsze wyświetlanie, proces systemowy zastąpi wyświetlane okno tła oknem głównego działania. W tym momencie użytkownik może zacząć korzystać z aplikacji.

Rysunek 1 przedstawia, jak procesy systemu i aplikacji przekazują sobie pracę.

Rysunek 1. Wizualna reprezentacja ważnych części uruchamiania aplikacji bez wcześniejszego uruchomienia.

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

Tworzenie aplikacji

Gdy aplikacja się uruchamia, puste okno startowe pozostaje na ekranie, dopóki system nie wyświetli aplikacji po raz pierwszy. W tym momencie proces systemowy zastąpi okno uruchamiania aplikacji, umożliwiając użytkownikowi interakcję z aplikacją.

Jeśli w swojej aplikacji zastąpisz metodę Application.onCreate(), system wywoła metodę onCreate() obiektu aplikacji. Następnie aplikacja tworzy wątek główny, zwany też wątkiem interfejsu użytkownika, 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

Gdy proces aplikacji utworzy aktywność, ta wykona te operacje:

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

Zwykle metoda onCreate() ma największy wpływ na czas wczytywania, ponieważ wykonuje zadania wymagające największego nakładu pracy: wczytuje i rozwija widoki oraz inicjuje obiekty potrzebne do działania aktywności.

Uruchamianie częściowo z pamięci

Uruchomienie „na ciepło” obejmuje podzbiór operacji, które mają miejsce podczas uruchamiania „na zimno”. Jednocześnie powoduje większy nakład pracy niż uruchomienie z pamięci. Istnieje wiele potencjalnych stanów, które można uznać za „ciepły start”. Oto niektóre z nich:

  • Użytkownik zamyka aplikację, ale uruchamia ją ponownie. Proces może nadal działać, ale aplikacja musi odtworzyć aktywność od podstaw, wywołując funkcję onCreate().

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

Uruchomienie z pamięci

Uruchomienie aplikacji z pamięci wymaga mniej zasobów niż uruchomienie „na zimno”. W przypadku szybkiego uruchamiania system przenosi Twoją aktywność na pierwszy plan. Jeśli wszystkie aktywności aplikacji są nadal w pamięci, aplikacja może uniknąć powtarzania inicjalizacji obiektów, tworzenia układu i renderowania.

Jeśli jednak część pamięci zostanie zwolnione w odpowiedzi na zdarzenia związane z oczyszczaniem pamięci, takie jak onTrimMemory(), te obiekty muszą zostać ponownie utworzone w odpowiedzi na zdarzenie szybkiego uruchamiania.

Uruchamianie z pamięci powoduje takie samo zachowanie na ekranie jak w przypadku uruchamiania „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, z których każdy rozpoczyna się od pierwszego rysunku ramki.

Jak zidentyfikować uruchamianie aplikacji w Perfetto

Aby debugować problemy z uruchamianiem aplikacji, warto określić, co dokładnie dzieje się na etapie uruchamiania aplikacji. Aby zidentyfikować całą fazę uruchamiania aplikacji w Perfetto:

  1. W Perfetto znajdź wiersz z danymi pochodnej Android App Startups. Jeśli nie widzisz tego komunikatu, spróbuj przechwycić ślad za pomocą aplikacji do śledzenia systemu na urządzeniu.

    Rysunek 3. Wartość pochodna z danych Czas uruchamiania aplikacji na Androida w Perfetto.
  2. Kliknij powiązany wycinek i naciśnij M, aby go wybrać. Wokół wycinka pojawiają się nawiasy, które wskazują, ile czasu zajęło wykonanie tego wycinka. Czas trwania jest też widoczny na karcie Bieżące zaznaczenie.

  3. Przypnij wiersz Uruchomienie aplikacji na Androida, klikając ikonę szpilki, która jest widoczna, gdy najedziesz kursorem na wiersz.

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

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

    Rys. 4. Wynik pochodnego wymiaru Czas uruchamiania aplikacji na Androida obok głównego wątku aplikacji.
  6. Dzięki temu możesz dokładniej debugować uruchomienie aplikacji.

Używanie danych do sprawdzania i ulepszania uruchamiania

Aby prawidłowo zdiagnozować czas uruchamiania, możesz śledzić dane, które pokazują, ile czasu zajmuje uruchomienie aplikacji. Android oferuje kilka sposobów informowania o problemach z aplikacją i pomaga je zdiagnozować. Android Vitals może Cię o tym poinformować, a narzędzia diagnostyczne pomogą Ci zdiagnozować problem.

Zalety korzystania z danych o rozruchu

Android używa danych czas do wyświetlenia pierwszej klatki (TTID)czas do wyświetlenia pełnej treści (TTFD) do optymalizacji uruchamiania aplikacji „na zimno” i „na ciepło”. Środowisko wykonawcze Androida (ART) korzysta z danych tych wskaźników, aby efektywnie wstępnie kompilować kod na potrzeby optymalizacji przyszłych uruchamianych aplikacji.

Szybsze uruchamianie aplikacji zwiększa jej trwałość w oczach użytkowników, co zmniejsza liczbę przypadków przedwczesnego jej zamykania, ponownego uruchamiania lub przełączania się na inną aplikację.

Android Vitals

Android Vitals może pomóc poprawić wydajność aplikacji, ostrzegając Cię w Konsoli Play, gdy czas uruchamiania aplikacji jest zbyt długi.

Android Vitals uznaje za nadmierne te czasy uruchamiania aplikacji:

Android Vitals używa danych Czas do wyświetlenia pierwszej klatki (TTID). Więcej informacji 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 na wyświetlenie pierwszej klatki interfejsu aplikacji. Ta wartość to czas potrzebny aplikacji na wygenerowanie pierwszej klatki, w tym inicjalizacja procesu podczas zimnego uruchamiania, tworzenie aktywności podczas zimnego lub ciepłego uruchamiania oraz wyświetlanie pierwszej klatki. Niski wskaźnik TTID aplikacji poprawia wrażenia użytkowników, ponieważ pozwala im szybko uruchomić aplikację. Identyfikator TTID jest raportowany automatycznie w przypadku każdej aplikacji przez interfejs Androida. Podczas optymalizacji uruchamiania aplikacji zalecamy wdrożenie reportFullyDrawn, aby wyświetlać informacje do TTFD.

TTID jest mierzony jako wartość czasowa, która reprezentuje łączny upłynął czas obejmujący następującą sekwencję zdarzeń:

  • Uruchamianie procesu.
  • Inicjowanie obiektów.
  • Tworzenie i inicjowanie aktywności.
  • Rozwijanie układu.
  • Rysowanie aplikacji po raz pierwszy.

Pobieranie identyfikatora TTID

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

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

Aby znaleźć TTID w Android Studio, wyłącz filtry w widoku Logcat w menu Filtry, a potem znajdź czas Displayed, jak pokazano na rysunku 5. Wyłączenie filtrów jest konieczne, ponieważ serwer systemowy, a nie sama aplikacja, obsługuje ten dziennik.

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

Dane Displayed w wyjściu Logcat niekoniecznie obejmują czas potrzebny do załadowania i wyświetlenia wszystkich zasobów. Nie zawiera zasobów, do których nie ma odwołań w pliku układu lub które aplikacja tworzy w ramach inicjalizacji obiektu. Wyklucza te zasoby, ponieważ ich wczytywanie jest procesem wbudowanym i nie blokuje początkowego wyświetlania aplikacji.

Czasami wiersz Displayed w wyjściu Logcat zawiera dodatkowe pole dotyczące łącznego czasu. Przykład:

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

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

Zalecamy korzystanie z Logcat w Android Studio, ale jeśli nie używasz Android Studio, możesz też mierzyć TTID, uruchamiając aplikację za pomocą polecenia adb menedżera aktywności w powłoce. 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 pozwalają określić 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 oraz treści wczytywanych asynchronicznie po wyświetleniu pierwszej klatki. Zazwyczaj 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 użycie aplikacji. Utrzymanie niskiego wskaźnika TTFD w aplikacji poprawia komfort korzystania z niej, ponieważ pozwala użytkownikom szybko wchodzić w interakcję z aplikacją.

System określa TTID, gdy Choreographer wywołuje metodę aktywności onDraw() i wie, że wywołuje ją po raz pierwszy. System nie wie jednak, kiedy określić TTFD, ponieważ każda aplikacja zachowuje się inaczej. Aby określić TTFD, aplikacja musi przekazać systemowi sygnał, gdy osiągnie stan pełnego wyświetlenia.

Pobieranie TTFD

Aby znaleźć TTFD, sygnalizujesz stan całkowicie narysowanego obiektu, 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 od momentu, gdy system otrzyma intencję uruchomienia aplikacji do momentu wywołania funkcji reportFullyDrawn(). Jeśli nie wywołasz funkcji reportFullyDrawn(), wartość TTFD nie jest raportowana.

Aby zmierzyć TTFD, wywołaj funkcję reportFullyDrawn() po całkowitym narysowaniu interfejsu użytkownika i wszystkich danych. Nie wywołuj funkcji reportFullyDrawn(), zanim okno pierwszej aktywności zostanie narysowane i wyświetlone zgodnie z czasem zmierzonym przez system, ponieważ wtedy system podaje zmierzony przez siebie czas. Innymi słowy, jeśli wywołasz funkcję reportFullyDrawn(), zanim system wykryje identyfikator TTID, system zgłasza identyfikator TTID i TTFD jako tę samą wartość, która jest wartością TTID.

Gdy użyjesz reportFullyDrawn(), narzędzie Logcat wyświetli dane wyjściowe podobne do tych poniżej, w których TTFD wynosi 1 s54 ms:

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

Dane wyjściowe Logcat zawierają czas total, jak opisano w sekcji Czas wyświetlania początkowego.

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

Możesz użyć reportFullyDrawn(), aby zasygnalizować stan w pełni narysowanego obiektu w podstawowych przypadkach, gdy wiesz, że stan w pełni narysowanego obiektu został osiągnięty. Jednak w przypadkach, gdy wątki w tle muszą ukończyć pracę w tle przed osiągnięciem stanu w pełni narysowanego, musisz opóźnić reportFullyDrawn(), aby uzyskać dokładniejszy pomiar TTFD. Aby dowiedzieć się, jak opóźnić reportFullyDrawn(), zapoznaj się z następnym akapitem.

Poprawianie dokładności czasu uruchamiania

Jeśli aplikacja wykonuje ładowanie opóźnione, a pierwotny wyświetlacz nie zawiera 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, aby uwzględnić populację listy w ramach swojego harmonogramu porównywania.

Jeśli na przykład interfejs użytkownika zawiera listę dynamiczną, taką jak RecyclerView lub listę opóźnioną, może ona zostać wypełniona przez zadanie w tle, które zostanie ukończone po pierwszym narysowaniu listy, a więc po tym, jak interfejs użytkownika zostanie oznaczony jako w pełni narysowany. W takich przypadkach populacja listy nie jest uwzględniana w benchmarkingu.

Aby uwzględnić populację listy w ramach ustawień czasu odniesienia, użyj funkcji FullyDrawnReporter za pomocą funkcji getFullyDrawnReporter() i dodaj do niej reportera w kodzie aplikacji. Po zakończeniu wypełniania listy przez zadanie w tle zwalniaj raportera.

Funkcja FullyDrawnReporter nie wywołuje metody reportFullyDrawn(), dopóki nie zostaną uwolnione wszystkie dodane raporty. Dodanie licznika do momentu zakończenia procesu w tle powoduje, że czasy uwzględniają też czas potrzebny na wypełnienie listy w danych dotyczących czasu uruchamiania. Nie zmienia to działania aplikacji dla użytkownika, ale pozwala uwzględnić w danych dotyczących czasu uruchamiania czas potrzebny na wypełnienie listy. Funkcja reportFullyDrawn() nie jest wywoływana, dopóki wszystkie zadania nie zostaną wykonane, niezależnie od kolejności.

Ten przykład pokazuje, jak można uruchamiać kilka zadań w tle jednocześnie, z których każde 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 {
                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 w pełni narysowanego obiektu:

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

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

Możesz też uzyskać informacje o potencjalnych wąskich gardłach dzięki śledzeniu wbudowanemu w metodach onCreate() aplikacji i działań. Aby dowiedzieć się więcej o inline tracing, zapoznaj się z dokumentacją funkcji Traceomówieniem śledzenia systemowego.

Rozwiązywanie typowych problemów

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

Ciężkie inicjowanie aplikacji

Szybkość uruchamiania może się pogorszyć, gdy kod zastąpi obiekt Application i wykonuje podczas inicjowania tego obiektu skomplikowane operacje lub złożoną logikę. Aplikacja może tracić czas na uruchamianie, jeśli podklasy Application wykonują inicjalizację, której jeszcze nie trzeba wykonywać.

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

Inne problemy występujące podczas inicjalizacji aplikacji to liczne lub znaczące zdarzenia związane z zbieraniem pamięci podręcznej oraz operacje wejścia/wyjścia z dysku wykonywane równolegle z inicjalizacją, które dodatkowo blokują proces inicjalizacji. Zbieranie zbędących zasobów jest szczególnie ważne w przypadku środowiska wykonawczego Dalvik. Środowisko wykonawcze Androida (ART) wykonuje zbieranie zbędących zasobów równolegle, co minimalizuje wpływ tej operacji.

Diagnozowanie problemu

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

śledzenie metody,

Uruchomienie narzędzia CPU Profiler ujawnia, że metoda callApplicationOnCreate() w końcu wywołuje metodę com.example.customApplication.onCreate. Jeśli narzędzie pokazuje, że te metody zajmują dużo czasu na wykonanie, zbadaj je dokładniej, aby sprawdzić, co się tam dzieje.

Śledzenie w tekście

Użyj śledzenia w ramach wiersza, aby zbadać prawdopodobne przyczyny, w tym:

  • Początkowa onCreate() funkcja aplikacji.
  • dowolne globalne obiekty typu singleton zainicjowane przez aplikację.
  • operacje we/wy na dysku, deserializacja lub pętle ciasne, które mogą występować podczas 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 inicjalizacja opóźniona. Innymi słowy, inicjuj tylko te obiekty, które są potrzebne od razu. Zamiast tworzyć globalne obiekty statyczne, zastosuj wzór singleton, w którym aplikacja inicjuje obiekty tylko wtedy, gdy ich potrzebuje.

Rozważ też użycie platformy do iniekcji zależności, takiej jak Hilt, która tworzy obiekty i zależności podczas ich pierwszego wstrzyknięcia.

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

Inicjalizacja intensywnej aktywności

Tworzenie aktywności często wymaga dużych nakładów pracy. Często można optymalizować te działania, aby poprawić wydajność. Do takich problemów należą:

  • powiększanie dużych lub złożonych układów;
  • Blokowanie rysowania ekranu na dysku lub operacji we/wy w sieci.
  • wczytywanie i dekodowanie bitmap;
  • Rastreowanie obiektów VectorDrawable.
  • Inicjowanie innych podsystemów aktywności.

Diagnozowanie problemu

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

śledzenie metody,

Korzystając z profilowania procesora, zwróć uwagę na konstruktory podklas Application i metody com.example.customApplication.onCreate() w aplikacji.

Jeśli narzędzie pokazuje, że te metody wykonują się bardzo długo, zbadaj je dokładniej, aby sprawdzić, co się tam dzieje.

Śledzenie w tekście

Użyj śledzenia w ramach wiersza, aby zbadać prawdopodobne przyczyny, w tym:

  • Początkowa funkcja onCreate() aplikacji.
  • Wszystkie globalne obiekty pojedyncze, które inicjuje.
  • operacje we/wy na dysku, deserializacja lub pętle ciasne, które mogą występować podczas wąskiego gardła;

Rozwiązania problemu

Może występować wiele potencjalnych wąskich gardeł, ale 2 najczęstsze problemy i ich rozwiązania to:

  • Im większa jest hierarchia widoku, tym więcej czasu zajmuje jej wczytywanie przez aplikację. Aby rozwiązać ten problem, wykonaj te 2 czynności:
    • Spłaszcz hierarchię widoku, zmniejszając liczbę zbędnych lub zagnieżdżonych układów.
    • Nie napełniaj części interfejsu, które nie muszą być widoczne podczas uruchamiania. Zamiast tego użyj obiektu ViewStub jako zastępnika podhierarchii, którą aplikacja może napompować w bardziej odpowiednim momencie.
  • Inicjowanie wszystkich zasobów w wątku głównym może też spowolnić uruchamianie. Aby rozwiązać ten problem, wykonaj te czynności:
    • Przesuń całą inicjalizację zasobów, aby aplikacja mogła ją wykonać w trybie opóźnionym w innym wątku.
    • Poczekaj, aż aplikacja wczyta i wyświetli widoki, a potem zaktualizuj właściwości wizualne, które zależą od bitmap i innych zasobów.

Niestandardowe ekrany powitalne

Jeśli wcześniej zastosowałeś(-aś) jedną z tych metod implementacji niestandardowego ekranu powitalnego w Androidzie 11 (interfejs API poziomu 30) lub starszym:

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

Od Androida 12 wymagana jest migracja do interfejsu API SplashScreen. Ten interfejs API umożliwia szybsze uruchamianie aplikacji i dostosowywanie ekranu powitalnego na następujące sposoby:

Ponadto biblioteka kompatybilności umożliwia wsteczne przeniesienie interfejsu API SplashScreen, aby zapewnić zgodność wsteczną i ujednolicić wygląd ekranu wczytywania we wszystkich wersjach Androida.

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