11 marca 2025 (updated: 11 marca 2025)

Przyspiesz wydajność swojej aplikacji za pomocą Android Profiler

Chapters

      Czy kiedykolwiek zauważyłeś nagłe zawieszenie aplikacji, którą stworzyłeś? Wyobraź sobie, że właśnie to się stało. Czy twoim następnym krokiem jest profilowanie procesu aplikacji za pomocą Android Profiler? Jeśli nie, napisałem ten artykuł specjalnie dla ciebie!

      Android Profiler to zestaw narzędzi dostępnych od Android Studio 3.0, które zastępują wcześniejsze narzędzia Android Monitor. Nowy zestaw jest znacznie bardziej zaawansowany w diagnozowaniu problemów z wydajnością aplikacji. Oferuje wspólny widok osi czasu oraz szczegółowe profilery CPU, pamięci i sieci. Umiejętnie go używając, możemy zaoszczędzić dużo czasu traconego na debugowanie lub przewijanie logów w oknie Logcat.

      Aby uzyskać dostęp do narzędzi profilowania, kliknij Widok > Okna narzędzi > Android Profiler lub znajdź odpowiednie okno narzędzia na pasku narzędzi. Aby zobaczyć dane w czasie rzeczywistym, musisz podłączyć urządzenie z włączonym debugowaniem USB lub użyć emulatora Android i mieć wybrany proces aplikacji. Zachęcam cię do przeczytania oficjalnego przewodnika użytkownika Android, aby dowiedzieć się, jak sprawdzić wszystkie dane wyświetlane w tym oknie.

      Czy lubisz uczyć się na przykładach? Przygotowałem dwa przykłady do ćwiczeń z CPU Profiler. To małe aplikacje, które napotkały pewne problemy z wydajnością. Spróbujmy je rozwiązać!

      Optymalizacja wydajności aplikacji na Androida

      Przykład 1

      Zaczniemy od stworzenia prostej aplikacji na Androida, która wyświetla listę sekwencyjnych dat (które jeszcze się nie wydarzyły). Pod każdą datą możemy wyświetlić pozostały czas w dniach, godzinach, minutach i sekundach.

      Kod z obu przykładów jest dostępny na GitHubie, więc możesz łatwo sklonować repozytorium i otworzyć projekt w Android Studio. Na razie sprawdź rewizję oznaczoną sample-1-before.

      Zacznij od zdefiniowania układu składającego się z RecyclerView umieszczonego wewnątrz SwipeRefreshLayout. Umożliwi to odświeżenie danych przy pionowym przesunięciu palcem.

      Następnie stwórz Activity, która inflatuje nasz układ, obsługuje interakcje użytkownika i wykonuje operacje na głównym wątku, aby wyświetlić odświeżone dane:

      W linii 9 używamy adaptera RecyclerView. Korzystamy z biblioteki recycler z android-commons (używanej w większości projektów Android EL Passion). Funkcja generyczna przyjmuje listę elementów, referencję do zasobu układu elementu i binder. Celem jest zachowanie zwięzłości kodu i skonfigurowanie adaptera RecyclerView bez zbędnego kodu szablonowego.

      Na końcu funkcji onCreate ustawiamy nasłuchiwacz, aby być powiadamianym o akcjach odświeżania wywołanych przez SwipeRefreshLayout. Odwołana funkcja refreshData zastępuje listę nowymi elementami i powiadamia adapter o jakiejkolwiek zmianie danych.

      W linii 25 generujemy listę 1000 elementów. Każdy element ustawia swoje właściwości w odniesieniu do aktualnej daty i czasu oraz przesunięcia w dniach. Przesunięcie przyjmuje wartości od 0 do 999 i wpływa na datę wyświetlaną przez element (zobacz linię 29). Używamy ThreeTenABP jako naszego API do dat i czasów. Jest to niezwykle cenny backport pakietu java.time.* zoptymalizowany przez Jake'a Whartona dla Androida.

      W linii 36 wykonujemy kilka operacji, aby uzyskać pozostały czas w bardziej zrozumiałym formacie.

      W linii 50 łączymy element z widokiem holdera, aby zaktualizować itemView na określonej pozycji. Uzyskujemy dostęp do zasobów, aby uzyskać sformatowany ciąg z wartością remainingTime.

      Sam element przechowuje wartości formattedDate i remainingTime, gotowe do wyświetlenia w odpowiednich komponentach TextView. Użyjmy następującego układu elementu:

      Uruchom aplikację i przesuń, aby odświeżyć dane. Czy zauważyłeś zacięcie? Prawdopodobnie nie. To w dużej mierze zależy od CPU twojego urządzenia i innych procesów zużywających czas CPU. Teraz uruchom narzędzie Android Profiler Tool Window i wybierz odpowiednią oś czasu, aby otworzyć CPU Profiler. Podłącz swoje urządzenie i przesuń, aby odświeżyć ponownie. Zauważ, że wątki profilu są dodawane do procesu aplikacji i zużywają dodatkowy czas CPU. Zakładam, że już doświadczyłeś pomijania klatek. Spójrz na Logcat, ponieważ choreograf powinien już ostrzec cię o intensywnym przetwarzaniu:

      I/Choreographer: Pominięto 147 klatek! Aplikacja może wykonywać zbyt wiele pracy w swoim głównym wątku.

      Świetnie! Możemy rozpocząć naszą inspekcję. Spójrz na oś czasu CPU Profiler:

      Oś czasu CPU Profiler

      Nad wykresem znajduje się widok reprezentujący interakcję użytkownika z aplikacją. Wszystkie zdarzenia wejściowe użytkownika pojawiają się tutaj jako fioletowe okręgi. Możesz zobaczyć jeden okrąg, który reprezentuje przesunięcie, które wykonaliśmy, aby odświeżyć dane. Nieco niżej możesz znaleźć aktualnie wyświetlaną Sample1Activity. Ten obszar nazywa się Event timeline.

      Poniżej zdarzeń znajduje się oś czasu CPU, która graficznie pokazuje użycie CPU przez aplikację i inne procesy w odniesieniu do całkowitego dostępnego czasu CPU. Co więcej, możesz obserwować liczbę wątków, które używa twoja aplikacja.

      Na dole możesz zobaczyć oś czasu aktywności wątków należących do procesu aplikacji. Każdy wątek jest w jednym z trzech stanów wskazywanych kolorami: aktywny (zielony), oczekujący (żółty) lub uśpiony (szary). Na górze listy możesz znaleźć główny wątek aplikacji. Na moim urządzeniu (Nexus 5X) używa ~35% czasu CPU przez około 5 sekund. To dużo! Możemy nagrać ślad metody, aby zobaczyć, co się tam dzieje.

      Kliknij przycisk Nagrywaj 🔴 tuż przed przesunięciem, aby odświeżyć akcję i zatrzymaj nagrywanie wkrótce po zakończeniu odświeżania danych. Kiedy skończysz, zauważ, że panel śladu metody właśnie się pojawił:

      nagrywanie profilu android

      Rozpoczniemy naszą analizę od wykresu wywołań wyświetlanego w pierwszej zakładce. Oś pozioma reprezentuje upływ czasu. Wywołujący i ich wywoływani (od góry do dołu) są wyświetlani na osi pionowej. Wywołania metod są również rozróżniane kolorem w zależności od tego, czy jest to wywołanie do systemowego API, API osób trzecich czy naszej metody. Zauważ, że całkowity czas dla każdego wywołania metody to suma czasu samej metody i czasu jej wywoływanych. Z tego wykresu możesz wywnioskować, że problem z wydajnością znajduje się gdzieś wewnątrz metody generateItems. Najedź kursorem na słupek, aby sprawdzić więcej szczegółów dotyczących upływu czasu. Możesz również dwukrotnie kliknąć słupek, aby zobaczyć deklarację metody w kodzie. Trudno jest wywnioskować więcej z tej zakładki, ponieważ wymaga to dużo powiększania i przewijania, więc przejdźmy do następnej zakładki.

      profiler android

      Wykres płomieni jest znacznie lepszy do ujawnienia, które metody zajmowały cenny czas CPU naszego urządzenia. Agreguje te same stosy wywołań, odwracając wykres z poprzedniej zakładki. Zamiast wielu krótkich poziomych słupków, wyświetlany jest jeden dłuższy słupek. Spójrz na to teraz:

      profiler android CPU time

      Znaleziono dwie podejrzane metody. Czy uwierzyłbyś, że getRemainingTime całkowity czas wykonania metody zajmie ponad 2 sekundy, a LocalDateTime.format ponad 1 sekundę czasu CPU?

      5

      Zauważ, że ten czas obejmuje również każdy okres, w którym wątek nie był aktywny. W prawym górnym rogu panelu śladu metody możesz przełączyć informacje o czasie, aby były wyświetlane w Czasie Wątku. Jeśli analizujemy pojedynczy wątek, może to być preferowana opcja, ponieważ pokazuje zużycie czasu CPU, które nie jest wpływane przez inne wątki.

      Dobrze, przejdźmy dalej. Teraz otwórz ostatnią zakładkę, aby zobaczyć wykres Bottom Up. Wyświetla listę wywołań metod posortowanych malejąco według zużycia czasu CPU. Ten wykres da nam szczegółowe informacje o czasie (w mikrosekundach). Rozszerzając metody, możesz znaleźć ich wywołujących.

      6

      Wyciągnij z wykresu informacje o czasie metod, które oskarżaliśmy o zbyt duże zużycie czasu CPU. Umieść je w odniesieniu do dwóch metod z ich stosu wywołań:

      Możesz zobaczyć, że getRemainingTime i LocalDateTime.format zużywają ponad 80% zarejestrowanego śladu metody! Aby naprawić to zacięcie, musimy popracować nad generowaniem elementów. To oczywiste.

      Co więc zrobić? Prawdopodobnie już wymyśliłeś kilka rozwiązań. Wykonujemy intensywne obliczenia, aby stworzyć 1000 elementów (to nie mała liczba). Możesz pomyśleć o wdrożeniu paginacji, aby stopniowo tworzyć i wyświetlać dane. To świetny pomysł, ponieważ będzie skalowalny. Jednak tym razem chciałbym pójść inną drogą. Co jeśli wykonamy całe formatowanie tuż przed wyświetleniem danych w RecyclerView na określonej pozycji — kiedy łączymy Item z RecyclerView.ViewHolder? Dzięki temu wywołamy metody getRemainingTime i LocalDateTime.format tylko dla kilku aktualnie wyświetlanych i gotowych do wyświetlenia elementów — a nie tysiąc razy jak wcześniej. Aby to osiągnąć, musimy zaktualizować właściwości Item, aby przechowywały tylko niezbędne dane do późniejszego formatowania:

      To wymaga zastosowania następujących zmian w funkcjach generateItems i bindItem:

      Zobaczmy, że włączyliśmy funkcję createItem, ponieważ całe formatowanie teraz odbywa się wewnątrz metody bindItem. Sprawdź rewizję oznaczoną sample-1-after, aby otrzymać te zmiany.

      Czas na ponowne uruchomienie CPU Profiler i nagranie śladu metody po wprowadzeniu zmian w naszym kodzie. Spójrz na wykres wywołań, aby sprawdzić, czy nasza optymalizacja poszła dobrze:

      7 Android Profiler Call Chart

      Jeśli najedziesz myszką na funkcję generateItems, dowiesz się, że teraz zużywa ~0.3 sekundy czasu zegarowego. To ponad 13 razy mniej czasu CPU niż przed optymalizacją! Zanim zaczniemy świętować, przełączmy się na wykres płomieni, aby upewnić się, że nasze zmiany nie mają negatywnego wpływu na całkowity czas metody bindItem. Na szczęście zużywa do 0.1 sekundy.

      8Dodatkowo możesz przewinąć, aby upewnić się, że nasza optymalizacja nie wpływa na ogólną wydajność aplikacji. Spróbuj nagrać ślad metody podczas takiego przewijania. Zobacz, że choreograf już nie narzeka na pomijanie klatek. Sukces! Kod został zoptymalizowany i zakończyliśmy pierwszy przykład!

      Przykład 2

      W następnym przykładzie w większości ponownie wykorzystamy kod z przykładu 1 po optymalizacji. Jedyną zmianą, jaką wprowadzimy, będzie układ aktywności. Dodamy ImageView nad RecyclerView. Aby cały content był przewijalny, umieść oba widoki wewnątrz NestedScrollView:

      Aby uniknąć konfliktów w zachowaniu przewijania RecyclerView, musimy ustawić atrybut nestedScrollingEnabled na false. Sprawdź rewizję oznaczoną sample-2-before, aby szybko pobrać ten przykład. Uruchom aplikację i przesuń, aby odświeżyć dane. Powinieneś zauważyć zacięcie, nawet bez podłączonego Android Profiler.

      Tym razem postanowiłem pozwolić Ci na samodzielną diagnozę, aby nie psuć Ci zabawy. Po udanej optymalizacji wydajności aplikacji nie powinieneś napotkać żadnych zacięć, jak w przykładzie 1. Jest tylko jedna zasada — ekran wyświetlany użytkownikowi nie może zmieniać swojego wyglądu. Powodzenia!

      Podsumowanie

      Uważam, że zachęciłem cię do częstszego korzystania z Android Profiler. Myślę, że to dobra praktyka, jeśli zależy nam na płynności doświadczeń użytkowników. W tym artykule skupiłem się głównie na CPU Profiler. Jednak zarówno Memory Profiler, jak i Network Profiler, które nie zostały omówione w tekście, również zasługują na uwagę. Rejestrowanie alokacji pamięci bardzo pomaga w znajdowaniu wycieków, np. obwiniając cię za nieusunięte Bitmaps. Tak czy inaczej, profilowanie aktywności sieciowej może prowadzić do wielu optymalizacji mających na celu zmniejszenie zużycia baterii.

       

      Paweł Gajda

      Senior Android Developer

      Może to początek pięknej przyjaźni?

      Jesteśmy dostępni dla nowych projektów.

      Contact us