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ą obniżać wydajność aplikacji. Poniżej znajdziesz typowe problemy, które należy wziąć pod uwagę:
- Czas oczekiwania na uruchomienie
Czas oczekiwania na uruchomienie to czas, jaki upływa od kliknięcia ikony aplikacji, powiadomienia lub innego punktu wejścia do wyświetlenia danych użytkownika na ekranie.
Staraj się realizować w swoich aplikacjach następujące cele związane ze start-upem:
Uruchomienie „na zimno” w czasie krótszym niż 500 ms. Uruchomienie zimnego startu ma miejsce, gdy uruchamiana aplikacja nie jest zapisana w pamięci systemu. Dzieje się tak przy pierwszym uruchomieniu aplikacji od momentu ponownego uruchomienia albo gdy proces aplikacji zostaje zatrzymany przez użytkownika lub system.
Uruchomienie częściowo z pamięci następuje wtedy, gdy aplikacja działa już w tle. Uruchomienie „na zimno” wymaga najwięcej pracy, ponieważ system musi wczytać wszystko z pamięci i zainicjować aplikację. Staraj się, aby uruchomienia „na zimno” trwały maksymalnie 500 ms.
Czasy oczekiwania P95 i P99 są bardzo zbliżone do mediany czasu oczekiwania. Gdy aplikacja zbyt długo się uruchamia, źle wpływa to na wygodę użytkowników. Komunikacja międzyprocesowa (IPC) i niepotrzebne operacje wejścia-wyjścia podczas krytycznej ścieżki uruchamiania aplikacji mogą powodować rywalizację o blokady i wprowadzać niespójności.
- Zacinanie podczas przewijania
Jank to termin opisujący wizualny problem, który występuje, gdy system nie jest w stanie utworzyć i dostarczyć klatek w odpowiednim czasie, aby wyświetlić je na ekranie z żądaną częstotliwością 60 Hz lub większą. Najbardziej zauważalny jest Jank podczas przewijania, ale zamiast płynnej animacji pojawiają się czkawki. Pojawia się, gdy ruch jest zatrzymywany na 1 lub więcej klatek, ponieważ renderowanie treści przez aplikację trwa dłużej niż długość klatki w systemie.
Aplikacje muszą być kierowane na częstotliwość odświeżania 90 Hz. Tradycyjne współczynniki renderowania wynoszą 60 Hz, ale wiele nowszych urządzeń działa w trybie 90 Hz podczas interakcji z użytkownikiem, na przykład podczas przewijania. Niektóre urządzenia obsługują jeszcze wyższe częstotliwości do 120 Hz.
Aby zobaczyć, jaka częstotliwość odświeżania jest używana przez urządzenie w danym momencie, włącz nakładkę, klikając Opcje programisty > 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. Takie przejścia muszą być płynnymi animacjami i nie mogą zawierać opóźnień ani migotania.
- Niewydajność energii
Praca obniża poziom naładowania baterii, a wykonywanie niepotrzebnych zadań skraca czas pracy baterii.
Przydziały pamięci wynikające z tworzenia nowych obiektów w kodzie mogą być przyczyną dużych nakładów pracy w systemie. Wynika to nie tylko z tego, że same przydziały wymagają pracy ze środowiska wykonawczego Android (ART), ale późniejsze zwolnienie tych obiektów (odczyszczanie pamięci) wymaga też czasu i wysiłku. Alokacja i zbieranie danych są znacznie szybsze i wydajniejsze, zwłaszcza w przypadku obiektów tymczasowych. Chociaż kiedyś unikanie przydzielania obiektów, gdy tylko jest to możliwe, zaleca się robić to, co najlepiej pasuje do aplikacji i architektury. Oszczędzanie na przydziałach, w których występuje ryzyko, że kod jest niemożliwy do obsługi, nie jest najlepszą metodą ze względu na możliwości ART.
Wymaga to jednak wysiłku, ale pamiętaj, że może przyczyniać się do problemów z wydajnością, jeśli przydzielasz wiele obiektów w wewnętrznej pętli.
Zidentyfikuj problemy
Aby wykryć i rozwiązać problemy ze skutecznością, zalecamy skorzystanie z tego procesu:
- Określ i przeanalizuj te najważniejsze ścieżki użytkownika:
- Typowe procesy uruchamiania, w tym z programu uruchamiającego i powiadomień.
- Ekrany, na których użytkownik przewija dane.
- Przejścia między ekranami.
- długotrwałe procesy, takie jak nawigacja lub odtwarzanie muzyki.
- Sprawdź, co dzieje się podczas poprzednich przepływów, korzystając z tych narzędzi do debugowania:
- Perfetto: umożliwia sprawdzanie, co dzieje się na całym urządzeniu, dzięki precyzyjnym danym o czasie.
- Program profilujący pamięci: pozwala sprawdzić, jakie przydziały pamięci działają na stercie.
- Simpleperf: wykres flamegraficzny pokazujący, które wywołania funkcji wykorzystują najwięcej procesora w określonym okresie. Gdy zauważysz w Systrace coś, co zajmuje dużo czasu, ale nie wiesz, dlaczego tak się dzieje, możesz skorzystać z narzędzia Simpleperf.
Aby zrozumieć i debugować te problemy z wydajnością, ważne jest ręczne debugowanie poszczególnych uruchomień testów. Nie możesz zastąpić poprzednich kroków przez analizę danych zbiorczych. Aby jednak zrozumieć, co widzą użytkownicy, i określić, kiedy mogą wystąpić regresje, musisz skonfigurować zbieranie danych w automatycznych testach oraz w terenie:
- Procesy uruchamiania
- Dane pól: Czas uruchomienia Konsoli Play
- Testy laboratoryjne: testowanie startupów za pomocą Macrobenchmark
- Janek
- Dane pól
- Konsola Play zawiera podstawowe wskaźniki: w Konsoli Play nie można zawęzić danych do konkretnej ścieżki użytkownika. Raportuje tylko ogólne problemy w aplikacji.
- Pomiary niestandardowe za pomocą
FrameMetricsAggregator
: za pomocąFrameMetricsAggregator
możesz rejestrować zacięte dane w konkretnym przepływie pracy.
- Testy laboratoryjne
- Przewijanie za pomocą analizy porównawczej.
- Test porównawczy zbiera dane o czasie renderowania klatek za pomocą poleceń
dumpsys gfxinfo
, które obejmują ścieżkę pojedynczego użytkownika. To sposób na zrozumienie zróżnicowania w zależności od konkretnej ścieżki użytkownika. DaneRenderTime
, które określają czas renderowania długich klatek, są ważniejsze przy identyfikowaniu regresji lub ulepszeń niż liczba zbędnych klatek.
- Dane pól
Problemy z weryfikacją linków aplikacji
Linki aplikacji to precyzyjne linki oparte na adresie URL Twojej witryny, które zostały zweryfikowane jako należące do Twojej witryny. Poniżej znajdziesz przyczyny niepowodzenia weryfikacji linku aplikacji.
- Zakresy filtrów intencji: do filtrów intencji dodaj tylko parametr
autoVerify
w przypadku adresów URL, na które Twoja aplikacja może odpowiadać. - Niezweryfikowane przekierowania protokołów: niezweryfikowane przekierowania po stronie serwera i subdomeny są uważane za zagrożenie dla bezpieczeństwa i niepowodzenie weryfikacji. Sprawiają, że wszystkie linki
autoVerify
kończą się niepowodzeniem. Na przykład przekierowywanie linków z protokołu HTTP do HTTPS, np. example.com na www.example.com, bez weryfikacji linków HTTPS może spowodować niepowodzenie weryfikacji. Pamiętaj, aby zweryfikować linki aplikacji, dodając filtry intencji. - Linki niemożliwe do zweryfikowania: dodanie do testów linków, których nie można zweryfikować, może spowodować, że system nie będzie weryfikować linków aplikacji w przypadku Twojej aplikacji.
- Niestabilne serwery: upewnij się, że serwery mogą łączyć się z aplikacjami klienckimi.
Konfigurowanie aplikacji pod kątem analizy skuteczności
Właściwa konfiguracja jest niezbędna do uzyskiwania dokładnych, powtarzalnych i przydatnych testów porównawczych z aplikacji. Przeprowadzaj testy w systemie jak najbliżej produkcji, eliminując źródła szumu. W sekcjach poniżej znajdziesz szereg czynności związanych z pakietem APK i systemem, które możesz wykonać, aby przygotować konfigurację testową. Niektóre z nich są zależne od konkretnego przypadku użycia.
Punkty śledzenia
Aplikacje mogą instrumentować swój kod za pomocą niestandardowych zdarzeń śledzenia.
Podczas rejestrowania śladów śledzenie ta wiąże się z pewnym niewielkim obciążeniem wynoszącym około 5 μs na sekcję, dlatego nie umieszczaj go w każdej metodzie. Śledzenie większych fragmentów pracy o długości >0,1 ms może dać cenny wgląd w wąskie gardła.
Uwagi na temat plików APK
Warianty debugowania mogą być pomocne przy rozwiązywaniu problemów i symbolizacji próbek stosu, ale mają duży wpływ na wydajność. Urządzenia z Androidem 10 (poziom interfejsu API 29) lub nowszym mogą korzystać z profileable android:shell="true"
w pliku manifestu, aby umożliwić profilowanie w kompilacjach wersji.
Użyj konfiguracji zmniejszania kodu klasy produkcyjnej. Zależnie od zasobów używanych przez aplikację może to mieć znaczny wpływ na wydajność. Niektóre konfiguracje ProGuard usuwają punkty śledzenia, dlatego rozważ usunięcie tych reguł w przypadku konfiguracji, na której testujesz dane.
Kompilacja
Skompiluj aplikację na urządzeniu do znanego stanu – zwykle speed
lub speed-profile
. Działania wykonywane w tle w sam raz na żądanie (JIT) mogą mieć znaczny wpływ na wydajność, która jest często osiągana, jeśli ponownie instalujesz plik APK pomiędzy uruchomieniami testów. Oto polecenie, które pozwala wykonać tę czynność:
adb shell cmd package compile -m speed -f com.google.packagename
Tryb kompilacji speed
całkowicie kompiluje aplikację. Tryb speed-profile
kompiluje aplikację zgodnie z profilem wykorzystanych ścieżek kodu, które są zbierane podczas korzystania z aplikacji. Spójne i prawidłowe zbieranie profili może być trudne, więc jeśli zdecydujesz się z nich korzystać, upewnij się, że zbierają one to, czego oczekujesz. Te profile znajdują się w tej lokalizacji:
/data/misc/profiles/ref/[package-name]/primary.prof
Test porównawczy umożliwia bezpośrednie określenie trybu kompilacji.
Uwagi dotyczące systemu
W przypadku pomiarów niskopoziomowych i wysokich kalibruj urządzenia. przeprowadzać porównania A/B na tym samym urządzeniu i w tej samej wersji systemu operacyjnego, Mogą występować znaczne wahania skuteczności nawet w przypadku tego samego typu urządzenia.
Na urządzeniach z dostępem do roota rozważ użycie skryptu lockClocks
do testów mikroporównawczych. Skrypty te wykonują między innymi te działania:
- Umieść procesory ze stałą częstotliwością.
- Wyłącz małe rdzenie i skonfiguruj GPU.
- Wyłącz ograniczanie termiczne.
Nie zalecamy używania skryptu lockClocks
do testów ukierunkowanych na wygodę użytkowników, takich jak uruchamianie aplikacji, testowanie DoU czy testowanie pod kątem błędów, ale może to być kluczowe do ograniczenia szumu w testach mikrotestów porównawczych.
W miarę możliwości korzystaj z platformy testowej, takiej jak makroporównawczy, która może zmniejszyć szum w pomiarach i zapobiec niedokładności pomiarów.
Powolne uruchamianie aplikacji: niepotrzebna aktywność na trampolinie
Aktywność związana z trampolinami może niepotrzebnie wydłużyć czas uruchamiania aplikacji, dlatego warto wiedzieć, czy robi to Twoja aplikacja. Jak widać na tym przykładzie, pierwszy element activityStart
bezpośrednio po nim występuje inny activityStart
, mimo że pierwsze działanie nie wygenerowało żadnych klatek.
Może się to zdarzyć zarówno w punkcie wejścia powiadomienia, jak i w zwykłym punkcie wejścia podczas uruchamiania aplikacji. Często można go rozwiązać przez refaktoryzację. Jeśli na przykład używasz tego działania do konfiguracji przed innym działaniem, przekształć go w komponent lub bibliotekę wielokrotnego użytku.
Niepotrzebne przydziały wyzwalające częste GC
Możesz zauważyć, że czyszczenie pamięci odbywa się częściej, niż przewidujesz w syntezie.
W tym przykładzie każde 10 sekund podczas długo trwającej operacji wskazuje na to, że aplikacja może przydzielać zadania niepotrzebnie, ale regularnie:
Podczas korzystania z narzędzia Memory Profiler możesz też zauważyć, że konkretny stos wywołań odpowiada za większość przydziałów. Nie musisz agresywnie eliminować wszystkich przydziałów, ponieważ może to utrudniać zarządzanie kodem. Zamiast tego zacznij od obszarów interaktywnych przydziałów.
Niezwykłe ramki
Proces tworzenia grafiki jest stosunkowo skomplikowany i w określaniu, czy użytkownik zobaczy upuszczoną klatkę, mogą występować pewne niuanse. W niektórych przypadkach platforma może „uratować” ramkę, korzystając z buforowania. Możesz jednak zignorować większość tych niuansów, aby zidentyfikować problematyczne klatki z perspektywy aplikacji.
Gdy aplikacja nie wymaga dużych nakładów pracy, punkty śledzenia Choreographer.doFrame()
są rysowane z częstotliwością 16,7 ms na urządzeniu z szybkością 60 kl./s:
Po pomniejszeniu obrazu i poruszaniu się po nim czasami można zauważyć, że tworzenie klatek trochę trwa, ale to nie problem, bo nie trwa to dłużej, niż jest to przydzielone 16,7 ms:
Zakłócenie tego standardowego rytmu jest spowodowane nieregularną ramką, jak widać na rysunku 5:
Możesz przećwiczyć ich rozpoznawanie.
W niektórych przypadkach trzeba powiększyć punkt śledzenia, aby uzyskać więcej informacji o tym, które widoki zostały zawyżone, lub co robi RecyclerView
. W innych przypadkach
może być konieczna dokładniejsza kontrola.
Więcej informacji o identyfikowaniu nieskutecznych ramek i debugowaniu ich przyczyn znajdziesz w artykule Powolne renderowanie.
Typowe błędy obiektu RecyclerView
Unieważnienie wszystkich danych kopii zapasowej obiektu RecyclerView
może prowadzić do długich czasów renderowania klatek i zacinania się. Aby zminimalizować liczbę widoków, które wymagają aktualizacji, unieważnij tylko te dane, które się zmienią.
Informacje o tym, jak uniknąć kosztownych wywołań notifyDatasetChanged()
, które powodują aktualizację, a nie zastąpienie jej całkowicie, znajdziesz w artykule Prezentowanie danych dynamicznych.
Jeśli nie wszystkie zagnieżdżone typy RecyclerView
są obsługiwane prawidłowo, może to spowodować, że za każdym razem wewnętrzne RecyclerView
będą w pełni odtwarzane. Każdy zagnieżdżony, wewnętrzny element RecyclerView
musi mieć ustawienie RecycledViewPool
, aby umożliwić ponowne wykorzystywanie widoków między poszczególnymi wewnętrznymi elementami RecyclerView
.
Jeśli nie pobiera się z wyprzedzeniem wystarczającej ilości danych lub nie robi tego w odpowiednim czasie, to dotarcie do końca listy przewijanej może być niekorzystne, gdy użytkownik musi czekać na więcej danych z serwera. Technicznie to nie jest problem, ponieważ nie przegapiono żadnego terminu renderowania klatek, ale możesz znacznie poprawić wygodę użytkowników, modyfikując czas i liczbę pobierania z wyprzedzeniem, tak aby użytkownik nie musiał czekać na dane.
Debugowanie aplikacji
Oto różne metody debugowania wydajności aplikacji. Obejrzyj film poniżej, aby dowiedzieć się, jak działa śledzenie systemu i jak używać narzędzia do profilowania w Android Studio.
Debugowanie uruchamiania aplikacji przy użyciu Systrace
Zapoznaj się z sekcją Czas uruchamiania aplikacji, aby poznać proces uruchamiania aplikacji. Obejrzyj też film poniżej, aby dowiedzieć się, jak przebiega śledzenie systemu.
Typy startupów możesz rozróżnić na następujących etapach:
- „Zimny start” – zacznij od utworzenia nowego procesu bez zapisanego stanu.
- Uruchomienie częściowo z pamięci: odtwarza aktywność podczas ponownego wykorzystywania procesu lub odtwarza go z zapisanym stanem.
- Uruchomienie z pamięci: powoduje ponowne uruchomienie aktywności i rozpoczyna się od inflacji.
Zalecamy przechwytywanie kodu przy użyciu aplikacji do śledzenia systemu na urządzeniu. W przypadku Androida 10 lub nowszego użyj Perfetto. W przypadku Androida 9 i starszych wersji użyj Systrace. Zalecamy też wyświetlanie plików śledzenia za pomocą internetowej przeglądarki Perfetto śledzenia. Więcej informacji znajdziesz w artykule Omówienie śledzenia systemu.
Zwróć uwagę na między innymi:
- Rywalizacja o monitorowanie: konkurencja o zasoby chronione monitorem może powodować znaczne opóźnienie w uruchamianiu aplikacji.
Synchroniczne transakcje powiązania: poszukaj niepotrzebnych transakcji na ścieżce krytycznej aplikacji. Jeśli wymagana transakcja jest kosztowna, rozważ współpracę z odpowiednim zespołem ds. platformy nad wprowadzeniem ulepszeń.
Równoczesne GC: to częsty i stosunkowo mały wpływ, jeśli jednak napotykasz taki problem często, możesz to sprawdzić za pomocą profilera pamięci Android Studio.
I/O: sprawdź, czy podczas uruchamiania wykryto wejścia i wyjścia, i poszukaj długich przerw.
Znaczna aktywność w innych wątkach: może to zakłócać wątek UI, dlatego podczas uruchamiania zwróć uwagę na działanie w tle.
Zalecamy wywołanie metody reportFullyDrawn
po zakończeniu uruchamiania z perspektywy aplikacji, aby usprawnić raportowanie danych dotyczących uruchamiania aplikacji. Więcej informacji o korzystaniu z reportFullyDrawn
znajdziesz w sekcji Czas do pełnego wyświetlenia.
Zdefiniowane w RFD czasy rozpoczęcia można wyodrębnić za pomocą procesora śledzenia Perfetto. Zdarzenie śledzenia widoczne dla użytkownika będzie emitowane.
Używaj śledzenia systemu na urządzeniu
Możesz użyć aplikacji na poziomie systemu zwanej Śledzeniem systemu, aby przechwytywać śledzenie systemu na urządzeniu. Ta aplikacja umożliwia rejestrowanie logów czasu z urządzenia bez konieczności podłączania go do adb
.
Używanie narzędzia Android Studio Memory Profiler
Możesz użyć narzędzia Android Studio Memory Profiler, aby sprawdzić wykorzystanie pamięci, które może być spowodowane wyciekami pamięci lub nieprawidłowymi wzorcami użytkowania. Zapewnia to podgląd na żywo przydziałów obiektów.
Problemy z pamięcią w aplikacji możesz rozwiązać, korzystając z informacji pochodzących z narzędzia Memory Profiler, aby śledzić, dlaczego i jak często występują GC.
Aby profilować pamięć aplikacji, wykonaj te czynności:
Wykrywaj problemy z pamięcią.
Zarejestruj sesję profilowania pamięci ścieżki użytkownika, na której chcesz się skupić. Poszukaj rosnącej liczby obiektów, jak pokazano na rys. 7, co ostatecznie prowadzi do kluczy GC, jak to widać na grafice 8.
Po zidentyfikowaniu ścieżki użytkownika, która zwiększa wykorzystanie pamięci, przeanalizuj główne przyczyny takiego obciążenia.
Diagnozuj największe przebarwienia pamięci.
Wybierz zakres na osi czasu, aby zwizualizować zarówno Przydziały, jak i Płytki rozmiar, jak pokazano na ilustracji 9.
Dane można sortować na kilka sposobów. Poniżej znajdziesz kilka przykładów, jak każdy z widoków może pomóc w analizie problemów.
Rozmieść według klasy: ta opcja jest przydatna, gdy chcesz znaleźć klasy, które generują obiekty, które są przechowywane w pamięci podręcznej lub używane ponownie z puli pamięci.
Jeśli na przykład zobaczysz, że co sekundę aplikacja tworzy 2000 obiektów klasy o nazwie „Vertex”, liczba przydziałów wzrasta o 2000 co sekundę i jest widoczna podczas sortowania według klasy. Jeśli chcesz ponownie używać tych obiektów, aby uniknąć czyszczenia pamięci, zaimplementuj pulę pamięci.
Rozmieść według schematu wywołań: przydatne, gdy chcesz znaleźć miejsce, w którym alokowana jest pamięć podręczna, np. w pętli lub w obrębie konkretnej funkcji wykonującej dużą część przydziału.
Płytki rozmiar: śledzi tylko pamięć samego obiektu. Jest to przydatne do śledzenia prostych klas złożonych głównie z wartości podstawowych.
Przechowywany rozmiar: pokazuje łączną ilość pamięci wiążącą się z obiektem i odwołaniami, do których ten obiekt odwołuje się wyłącznie. Przydaje się do śledzenia obciążenia pamięci spowodowanym złożonymi obiektami. Aby uzyskać tę wartość, zrób pełny zrzut pamięci, jak pokazano na rys. 10, i dodaj Zachowany rozmiar w postaci kolumny, jak widać na grafice 11.
Pomiar wpływu optymalizacji.
Wygenerowane dane są bardziej widoczne i łatwiejsze do zmierzenia wpływu optymalizacji pamięci. Gdy optymalizacja zmniejsza wykorzystanie pamięci, odnotowujesz mniej GC.
Aby zmierzyć wpływ optymalizacji, na osi czasu narzędzia do profilowania zmierz czas między GC. Przekonasz się, że czas między kolejnymi GC potrwa dłużej.
Skutki ulepszenia pamięci są następujące:
- Wyłączenia z braku pamięci są prawdopodobnie rzadsze, jeśli aplikacja nie wyczerpuje pamięci przez cały czas.
- Mniejsza liczba zaciemniania pamięci poprawia wskaźniki zaburzeń, zwłaszcza w wersji P99. Dzieje się tak, ponieważ procesory GC powodują rywalizację o wydajność procesora, co może prowadzić do wstrzymywania zadań renderowania podczas ich działania.
Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Analiza i optymalizacja uruchamiania aplikacji {:#app-startup-analysis-Optimizing}
- Zablokowane klatki
- Tworzenie makroporównania