Ten dokument pomoże Ci zidentyfikować i rozwiązać kluczowe problemy z wydajnością aplikacji.
Najważniejsze problemy z wydajnością
Istnieje wiele problemów, które mogą przyczyniać się do słabej skuteczności aplikacji, ale oto kilka typowych problemów, które mogą wystąpić w Twojej aplikacji:
- Opóźnienie uruchomienia
Opóźnienie uruchamiania to czas od kliknięcia ikony aplikacji, powiadomienia lub innego punktu wejścia do wyświetlenia danych użytkownika na ekranie.
W swoich aplikacjach dążyć do osiągnięcia tych celów uruchamiania:
Uruchomienie „na zimno” w mniej niż 500 ms. Uruchomienie „na zimno” ma miejsce, gdy uruchamiana aplikacja nie jest obecna w pamięci systemu. Dzieje się tak, gdy aplikacja jest uruchamiana po raz pierwszy od ponownego uruchomienia lub zatrzymania procesu przez użytkownika lub system.
Uruchomienie „na ciepło” ma miejsce, gdy aplikacja działa już w tle. Uruchomienie „na zimno” wymaga od systemu największego nakładu pracy, ponieważ musi on załadować wszystko z pamięci masowej i inicjializować aplikację. Postaraj się, aby czas uruchomień „na zimno” nie przekraczał 500 ms.
Czasy oczekiwania P95 i P99 są bardzo zbliżone do mediany. Długi czas uruchamiania aplikacji może powodować niewygodę użytkowników. Komunikacja między procesami (IPC) i niepotrzebne operacje wejścia/wyjścia na krytycznej ścieżce uruchamiania aplikacji mogą powodować rywalizację o blokadę i niespójności.
- Jankowanie podczas przewijania
Jank to termin opisujący zakłócenie wizualne, które występuje, gdy system nie jest w stanie tworzyć i przekazywać klatek na czas, aby wyświetlać je na ekranie z wymaganą częstotliwością 60 Hz lub wyższą. Najbardziej widoczne jest to podczas przewijania, gdy zamiast płynnego przejścia występują zacięcia. Jank występuje, gdy ruch jest wstrzymywany na co najmniej 1 klatkę, ponieważ renderowanie treści przez aplikację trwa dłużej niż czas trwania klatki w systemie.
Aplikacje muszą być kierowane na częstotliwość odświeżania 90 Hz. Konwencjonalne częstotliwości renderowania wynoszą 60 Hz, ale wiele nowszych urządzeń działa w trybie 90 Hz podczas interakcji z użytkownikiem, np. przewijania. Niektóre urządzenia obsługują nawet wyższe częstotliwości do 120 Hz.
Aby sprawdzić, z jakiej częstotliwości odświeżania korzysta urządzenie w danym momencie, włącz nakładkę, korzystając z opcji Opcje dewelopera > Pokaż częstotliwość odświeżania w sekcji Debugowanie.
- Przejścia, które nie są płynne
Jest to widoczne podczas interakcji takich jak przełączanie się między kartami czy wczytywanie nowej aktywności. Te typy przejść muszą być płynnymi animacjami bez opóźnień ani migotania.
- Nieefektywne wykorzystanie energii
Wykonywanie zadań powoduje zużycie baterii, a wykonywanie niepotrzebnych zadań skraca czas jej pracy.
Przydzielanie pamięci, które wynika z tworzenia nowych obiektów w kodzie, może być przyczyną znacznego obciążenia systemu. Dzieje się tak, ponieważ nie tylko same alokacje wymagają pracy od środowiska wykonawczego Androida (ART), ale także zwolnienie tych obiektów później (zbieranie zbędących zasobów) wymaga czasu i wysiłku. Przydział i zbieranie są znacznie szybsze i bardziej wydajne, zwłaszcza w przypadku obiektów tymczasowych. Chociaż w przeszłości zalecaliśmy unikanie przydzielania obiektów, obecnie zalecamy, aby robić to w sposób najbardziej odpowiedni dla Twojej aplikacji i jej architektury. Oszczędzanie na alokacjach kosztem możliwości utrzymania kodu nie jest dobrą praktyką, biorąc pod uwagę możliwości ART.
Wymaga to jednak wysiłku, a jeśli przydzielisz wiele obiektów w pętli wewnętrznej, może to spowodować problemy z wydajnością.
Identyfikowanie problemów
Aby zidentyfikować i rozwiązać problemy z wydajnością, zalecamy wykonanie tych czynności:
- Zidentyfikuj i sprawdź te kluczowe ścieżki użytkownika:
- Typowe procesy uruchamiania, w tym z menu z aplikacjami i powiadomień.
- Ekrany, na których użytkownik może przewijać dane.
- przejścia między ekranami.
- Długie procesy, takie jak nawigacja czy odtwarzanie muzyki.
- Sprawdzanie, co dzieje się podczas poprzednich przepływów, za pomocą tych narzędzi do debugowania:
- Perfetto: pozwala zobaczyć, co dzieje się na całym urządzeniu, z dokładnymi danymi o czasie.
- Profiler pamięci: pozwala sprawdzić, jakie alokacje pamięci są wykonywane w stosie.
- Simpleperf: pokazuje wykres płomienisty, który wskazuje, które wywołania funkcji zużywają najwięcej procesora w określonym czasie. Jeśli w Systrace wykryjesz coś, co zajmuje dużo czasu, ale nie wiesz, dlaczego, możesz uzyskać dodatkowe informacje za pomocą Simpleperf.
Aby zrozumieć te problemy z wydajnością i je debugować, musisz ręcznie debugować poszczególne testy. Nie możesz zastąpić poprzednich kroków analizą danych zagregowanych. Aby jednak zrozumieć, co widzą użytkownicy, i stwierdzić, kiedy mogą wystąpić regresje, ważne jest skonfigurowanie zbierania danych w ramach testów automatycznych i w polu:
- Sekwencje uruchamiania
- Dane dotyczące pola: czas uruchamiania Konsoli Play
- Testy laboratoryjne: test uruchamiania z użyciem Macrobenchmark
- Jank
- Dane dotyczące pól
- Dane dotyczące liczby klatek w Konsoli Play: w Konsoli Play nie możesz zawęzić danych do konkretnej ścieżki użytkownika. Raportuje tylko ogólne opóźnienia w całej aplikacji.
- pomiar niestandardowy za pomocą
FrameMetricsAggregator
: możesz używać funkcjiFrameMetricsAggregator
do rejestrowania danych o zakłóceniach podczas określonego procesu.
- Testy laboratoryjne
- Przewijanie za pomocą makrobenchmarku.
- Test makrobenchmarku zbiera informacje o czasie trwania poszczególnych klatek za pomocą poleceń
dumpsys gfxinfo
, które obejmują ścieżkę pojedynczego użytkownika. Dzięki temu możesz poznać różnice w zakłóceniach w przypadku konkretnej ścieżki użytkownika. DaneRenderTime
, które wskazują, jak długo trwa renderowanie klatki, są ważniejsze niż liczba klatek z zakłóceniami w przypadku wykrywania regresji lub ulepszeń.
- Dane dotyczące pól
Problemy z weryfikacją linków aplikacji
Linki aplikacji to precyzyjne linki oparte na adresie URL witryny, które zostały zweryfikowane jako należące do Twojej witryny. Poniżej podajemy powody, dla których weryfikacja App Link może się nie udać.
- Zakresy filtra intencji: dodaj
autoVerify
tylko do filtrów intencji dla adresów URL, na które może odpowiadać Twoja aplikacja. - Niesprawdzone przełączenia protokołów: niezweryfikowane przekierowania po stronie serwera i na subdomenę są uznawane za zagrożenie dla bezpieczeństwa i nie przechodzą weryfikacji. powodują niepowodzenie wszystkich linków
autoVerify
. Na przykład przekierowanie linków z HTTP na HTTPS, na przykład z example.com na www.example.com, bez weryfikacji linków HTTPS, może spowodować niepowodzenie weryfikacji. Pamiętaj, aby zweryfikować App Links, dodając filtry intencji. - Linki, których nie można zweryfikować: dodanie nieweryfikowalnych linków na potrzeby testów może spowodować, że system nie zweryfikuje linków aplikacji w Twojej aplikacji.
- Niepewne serwery: upewnij się, że Twoje serwery mogą łączyć się z aplikacjami klientów.
Konfigurowanie aplikacji pod kątem analizy wydajności
Aby uzyskać dokładne, powtarzalne i przydatne wyniki testów porównawczych aplikacji, musisz odpowiednio skonfigurować system. Testuj na systemie, który jest jak najbardziej zbliżony do środowiska produkcyjnego, jednocześnie eliminując źródła zakłóceń. W następnych sekcjach znajdziesz kilka kroków, które możesz wykonać, aby przygotować konfigurację testową. Niektóre z nich są zależne od przypadku użycia.
punkty kontrolne,
Aplikacje mogą wykorzystywać swój kod do niestandardowych zdarzeń śledzenia.
Podczas rejestrowania śladów generowany jest niewielki narzut około 5 μs na sekcję, dlatego nie należy stosować go w przypadku każdej metody. Prześledzenie większych fragmentów kodu, których wykonanie zajmuje ponad 0,1 ms, może dostarczyć istotnych informacji o wąskich gardłach.
Informacje o plikach APK
Warianty debugowania mogą być przydatne podczas rozwiązywania problemów i symbolizacji próbek stosu, ale mają poważny wpływ na wydajność. Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym można użyć w pliku manifestu atrybutu profileable android:shell="true"
, aby włączyć profilowanie w wersjach wydania.
Użyj konfiguracji skracania kodu przeznaczonej do wersji produkcyjnej. W zależności od zasobów używanych przez aplikację może to mieć znaczny wpływ na wydajność. Niektóre konfiguracje ProGuarda usuwają punkty śledzenia, dlatego rozważ usunięcie tych reguł w przypadku konfiguracji, na której przeprowadzasz testy.
Kompilacja
Zkompiluj aplikację na urządzeniu do znanego stanu – zazwyczaj speed
dla uproszczenia lub speed-profile
, aby bardziej zbliżyć wydajność do wersji produkcyjnej (chociaż wymaga to rozgrzania aplikacji i zapisywania profili lub kompilowania profili referencyjnych aplikacji).
Zarówno speed
, jak i speed-profile
zmniejszają ilość kodu interpretowanego przez dex, a w konsekwencji ilość kompilacji w tle w czasie rzeczywistym (JIT), która może powodować znaczne zakłócenia. Tylko speed-profile
zmniejsza wpływ ładowania klasy w czasie wykonywania z dex.
To polecenie kompiluje aplikację w trybie speed
:
adb shell cmd package compile -m speed -f com.example.packagename
Tryb kompilacji speed
kompiluje wszystkie metody aplikacji. Tryb speed-profile
kompiluje metody i klasy aplikacji zgodnie z profilem wykorzystanych ścieżek kodu zebranym podczas korzystania z aplikacji. Zbieranie profili może być trudne, dlatego jeśli zdecydujesz się na ich użycie, sprawdź, czy zbierają one oczekiwane dane. Profile znajdują się w tych miejscach:
/data/misc/profiles/ref/[package-name]/primary.prof
Uwagi dotyczące systemu
W przypadku pomiarów o niskiej i wysokiej wierności należy skalibrować urządzenia. Przeprowadzanie porównań A/B na tym samym urządzeniu i z użyciem tej samej wersji systemu operacyjnego. Wyniki mogą się znacznie różnić nawet na urządzeniach tego samego typu.
Na urządzeniach z rootem rozważ użycie skryptu lockClocks
do przeprowadzania mikrotestów. Skrypty te wykonują między innymi te czynności:
- Ustaw procesory na stałą częstotliwość.
- Wyłącz małe rdzenie i skonfiguruj procesor graficzny.
- Wyłącz ograniczanie temperatury.
Nie zalecamy używania skryptu lockClocks
do testów związanych z wrażeniami użytkowników, takich jak testowanie uruchamiania aplikacji, testowanie DoU i testowanie płynności, ale może on być niezbędny do zmniejszenia szumu w testach mikrobenchmarków.
Jeśli to możliwe, rozważ użycie platformy testowej, takiej jak Macrobenchmark, która może zmniejszyć szum w pomiarach i zapobiec ich niedokładności.
Powolne uruchamianie aplikacji: niepotrzebna aktywność trampoliny
Aktywność trampoliny może niepotrzebnie wydłużać czas uruchamiania aplikacji, dlatego warto sprawdzić, czy tak się dzieje. Jak widać w tym przykładowym śladzie, po jednym activityStart
następuje od razu kolejny activityStart
, bez generowania żadnych klatek przez pierwsze działanie.
Może się to zdarzyć zarówno w punkcie wejścia powiadomienia, jak i w zwykłym punkcie wejścia aplikacji. Często można to rozwiązać, przeprowadzając refaktoryzację. Jeśli na przykład używasz tej czynności do wykonania konfiguracji przed uruchomieniem innej czynności, umieść ten kod w komponencie lub bibliotece, którą można wielokrotnie używać.
Niepotrzebne przypisania powodujące częste GC
W pliku Systrace możesz zauważyć, że usuwanie elementów zbędnych (GC) występuje częściej, niż się spodziewasz.
W tym przykładzie co 10 sekund podczas długotrwałej operacji występuje wskaźnik, że aplikacja może niepotrzebnie, ale konsekwentnie przydziela czas procesora:
Możesz też zauważyć, że podczas korzystania z profilatora pamięci określony stos wywołań odpowiada za zdecydowaną większość alokacji. Nie musisz eliminować wszystkich alokacji w agresywny sposób, ponieważ może to utrudnić utrzymanie kodu. Zamiast tego zacznij od obszarów o największym natężeniu alokacji.
Niestabilne klatki
System przetwarzania grafiki jest dość skomplikowany i może się zdarzyć, że trudno będzie określić, czy użytkownik zobaczy utracony obraz. W niektórych przypadkach platforma może „ratować” kadr, stosując buforowanie. Możesz jednak zignorować większość tych niuansów, aby zidentyfikować problematyczne klatki z perspektywy aplikacji.
Gdy rysowanie klatek wymaga od aplikacji niewielkiej ilości pracy, punkty śladu występują co 16,7 ms na urządzeniu z częstotliwością 60 FPS:Choreographer.doFrame()
Jeśli oddalisz widok i przejdziesz przez ślad, zauważysz, że czasami generowanie niektórych klatek trwa nieco dłużej, ale nie przekracza przydzielonego czasu 16,7 ms:
Jeśli zauważysz przerwę w tym regularnym rytmie, oznacza to, że występuje klatka z niestabilnym obrazem, jak pokazano na rysunku 5:
Możesz ćwiczyć ich rozpoznawanie.
W niektórych przypadkach musisz powiększyć punkt śledzenia, aby uzyskać więcej informacji o tym, które wyświetlenia są zawyżone lub co robi RecyclerView
. W innych przypadkach konieczne może być dokładniejsze sprawdzenie.
Więcej informacji o identyfikowaniu klatek z zakłóceniami i ich debugowaniu znajdziesz w artykule Wydłużone renderowanie.
Typowe błędy związane z RecyclerView
Nieuzasadnione unieważnienie wszystkich danych pomocniczych RecyclerView
może spowodować długi czas renderowania klatek i problemy z płynnością. Aby zminimalizować liczbę widoków, które trzeba zaktualizować, unieważniaj tylko dane, które się zmieniły.
Więcej informacji o unikaniu kosztownych wywołań funkcji notifyDatasetChanged()
, które powodują aktualizację treści zamiast ich całkowitego zastąpienia, znajdziesz w artykule Przedstawianie danych dynamicznych.
Jeśli nie obsługujesz prawidłowo wszystkich zagnieżdżonych RecyclerView
, może to spowodować, że wewnętrzny RecyclerView
będzie za każdym razem całkowicie odtwarzany. Każdy zagnieżdżony RecyclerView
musi mieć ustawioną wartość RecycledViewPool
, aby zapewnić możliwość ponownego użycia widoków w każdym zagnieżdżonym RecyclerView
.
Niewystarczające wstępne pobieranie danych lub niewystarczające wstępne pobieranie danych w odpowiednim czasie może spowodować, że dotarcie do dołu listy może być nieprzyjemne dla użytkownika, który musi czekać na więcej danych z serwera. Chociaż technicznie nie jest to płynne, ponieważ nie są pomijane żadne ramy czasowe, możesz znacznie poprawić UX, zmieniając czas i ilość wstępnego pobierania, aby użytkownik nie musiał czekać na dane.
Debugowanie aplikacji
Poniżej znajdziesz różne metody debugowania wydajności aplikacji. Obejrzyj ten film, aby dowiedzieć się więcej o śledzeniu systemu i profilowaniu w Android Studio.
Debugowanie uruchamiania aplikacji za pomocą Systrace
Informacje o procesie uruchamiania aplikacji znajdziesz w artykule Czas uruchamiania aplikacji, a omówienie śledzenia systemu w tym filmie.
Typy uruchamiania możesz rozróżniać na tych etapach:
- Uruchomienie „na zimno”: rozpocznij od utworzenia nowego procesu bez zapisanego stanu.
- Ciepły rozruch: albo odtwarza aktywność przy ponownym użyciu procesu, albo odtwarza proces z zapisanym stanem.
- Szybkie uruchamianie: uruchamia ponownie aktywność i rozpoczyna ją od momentu załadowania.
Zalecamy rejestrowanie zdarzeń Systray za pomocą aplikacji System Tracing na urządzeniu. W przypadku Androida 10 lub nowszego używaj Perfetto. W przypadku Androida 9 lub starszego użyj Systrace. Zalecamy też wyświetlanie plików śledzenia za pomocą przeglądarki Perfetto dostępnej w internecie. Więcej informacji znajdziesz w artykule Omówienie śledzenia systemu.
Oto kilka rzeczy, na które warto zwrócić uwagę:
- Rywalizacja o zasoby chronione przez monitor: rywalizacja o zasoby chronione przez monitor może spowodować znaczne opóźnienie uruchamiania aplikacji.
Transakcje synchroniczne: szukaj niepotrzebnych transakcji na ścieżce krytycznej aplikacji. Jeśli niezbędna transakcja jest kosztowna, rozważ współpracę z odpowiednim zespołem platformy w celu wprowadzenia ulepszeń.
Jednoczesne GC: jest to częsty i względnie niewielki problem, ale jeśli często się pojawia, możesz go zbadać za pomocą profilowania pamięci w Android Studio.
Wejścia/wyjścia: sprawdź, czy podczas uruchamiania występują operacje wejścia-wyjścia, i sprawdź, czy występują długie przerwy.
Znacząca aktywność w innych wątkach: może ona zakłócać działanie wątku interfejsu użytkownika, dlatego zwracaj uwagę na działanie w tle podczas uruchamiania.
Aby poprawić raportowanie danych o uruchamianiu aplikacji, zalecamy wywołanie funkcji reportFullyDrawn
po zakończeniu uruchamiania aplikacji. Więcej informacji o używaniu elementu reportFullyDrawn
znajdziesz w sekcji Czas wyświetlania pełnego ekranu.
Czasy rozpoczęcia zdefiniowane za pomocą RFD możesz wyodrębnić za pomocą procesora śladu Perfeto. Wygenerowane zostanie widoczne dla użytkownika zdarzenie śladu.
Korzystanie z śledzenia systemu na urządzeniu
Aby utworzyć ślad systemu na urządzeniu, możesz użyć aplikacji System Tracing. Ta aplikacja umożliwia rejestrowanie śladów z urządzenia bez konieczności podłączania go do adb
.
Korzystanie z Android Studio Memory Profiler
Za pomocą profilatora pamięci w Android Studio możesz sprawdzić obciążenie pamięci, które może być spowodowane wyciekami pamięci lub nieprawidłowym wzorcem użycia. Udostępnia ona podgląd na żywo alokacji obiektów.
Problemy z pamięcią w aplikacji możesz rozwiązać, korzystając z informacji z profilu pamięci, aby śledzić, dlaczego i jak często występują GC.
Aby przeanalizować pamięć aplikacji, wykonaj te czynności:
wykrywać problemy z pamięcią;
Nagraj sesję profilowania pamięci na ścieżce użytkownika, na której chcesz się skupić. Zwróć uwagę na rosnącą liczbę obiektów, jak na rysunku 7, która ostatecznie prowadzi do wystąpienia GC, jak na rysunku 8.
Po zidentyfikowaniu ścieżki użytkownika, która zwiększa obciążenie pamięci, znajdź przyczyny tego obciążenia.
diagnozować miejsca występowania problemów z obciążeniem pamięci;
Wybierz zakres na osi czasu, aby wyświetlić zarówno przypisania, jak i mały rozmiar, jak pokazano na rysunku 9.
Dane te można sortować na wiele sposobów. Poniżej znajdziesz kilka przykładów, które pokazują, jak poszczególne widoki mogą Ci pomóc w analizowaniu problemów.
Uporządkuj według klasy: przydatne, gdy chcesz znaleźć klasy, które generują obiekty, które są inaczej przechowywane w pamięci podręcznej lub używane ponownie z pulu pamięci.
Jeśli np. aplikacja co sekundę tworzy 2000 obiektów klasy „Vertex”, liczba przydziałów zwiększa się o 2000 co sekundę i widać to podczas sortowania według klasy. Jeśli chcesz ponownie używać tych obiektów, aby uniknąć generowania śmieci, zaimplementuj pulę pamięci.
Uporządkuj według ścieżki wywołania: przydatne, gdy chcesz znaleźć gorącą ścieżkę, na której przydziela się pamięć, np. wewnątrz pętli lub w konkretnej funkcji wykonującej dużo pracy przy przydzielaniu.
Shallow Size (Mały rozmiar): śledzi tylko pamięć samego obiektu. Jest ona przydatna do śledzenia prostych klas, które składają się głównie z wartości prymitywnych.
Zachowana wielkość: pokazuje łączną ilość pamięci zajętej przez obiekt i odwołania, do których odwołuje się tylko ten obiekt. Jest to przydatne do śledzenia obciążenia pamięci spowodowanego przez złożone obiekty. Aby uzyskać tę wartość, wykonaj pełny zrzut pamięci, jak pokazano na rysunku 10. Zatrzymana wielkość jest dodawana jako kolumna, jak pokazano na rysunku 11.
Pomiar wpływu optymalizacji.
W przypadku GC jest łatwiej zauważyć i zmierzyć wpływ optymalizacji pamięci. Gdy optymalizacja zmniejsza obciążenie pamięci, widzisz mniej procesów GC.
Aby zmierzyć wpływ optymalizacji, na osi czasu w profilu zmierz czas między GC. Wtedy zauważysz, że między GC mija więcej czasu.
Ostatecznie ulepszenia dotyczące pamięci przynoszą następujące korzyści:
- Wyłączanie aplikacji z powodu braku pamięci prawdopodobnie będzie rzadsze, jeśli aplikacja nie będzie stale odczuwać presji pamięci.
- Mniej wywołań GC poprawia wskaźniki płynności, zwłaszcza P99. Dzieje się tak, ponieważ GC powoduje rywalizację o procesor, co może prowadzić do opóźnienia zadań renderowania podczas GC.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Analiza i optymalizacja uruchamiania aplikacji {:#app-startup-analysis-optimization}
- Zablokowane klatki
- Tworzenie testu porównawczego makro