Ten dokument pomoże Ci zidentyfikować i rozwiązać kluczowe problemy z wydajnością aplikacji.
Najważniejsze problemy z wydajnością
Na słabą skuteczność aplikacji może wpływać wiele problemów, ale oto kilka typowych, na które warto zwrócić uwagę:
- Opóźnienie uruchomienia
Opóźnienie uruchomienia to czas, jaki upływa od kliknięcia ikony aplikacji, powiadomienia lub innego punktu wejścia do momentu wyświetlenia danych użytkownika na ekranie.
W przypadku aplikacji staraj się osiągnąć te cele dotyczące uruchamiania:
Uruchamianie „na zimno” w mniej niż 500 ms. Uruchomienie „na zimno” następuje, gdy uruchamiana aplikacja nie jest obecna w pamięci systemu. Dzieje się tak, gdy aplikacja jest uruchamiana po raz pierwszy od ponownego uruchomienia urządzenia lub od zatrzymania procesu aplikacji przez użytkownika lub system.
Natomiast uruchomienie „na ciepło” następuje, gdy aplikacja jest już uruchomiona w tle. Uruchomienie „na zimno” wymaga od systemu najwięcej pracy, ponieważ musi on wczytać wszystko z pamięci i zainicjować aplikację. Staraj się, aby uruchomienia „na zimno” trwały nie dłużej niż 500 ms.
Czasy oczekiwania P95 i P99 są bardzo zbliżone do mediany czasu oczekiwania. Gdy aplikacja długo się uruchamia, może to negatywnie wpłynąć na wygodę użytkowników. Komunikacja międzyprocesowa (IPC) i niepotrzebne operacje wejścia/wyjścia na ścieżce krytycznej uruchamiania aplikacji mogą powodować spory o blokady i wprowadzać niespójności.
- Zacinanie się przewijania
Zacięcie to termin opisujący wizualne zakłócenie, które występuje, gdy system nie jest w stanie zbudować i dostarczyć klatek na czas, aby wyświetlić je na ekranie z wymaganą częstotliwością 60 Hz lub wyższą. Jank jest najbardziej widoczny podczas przewijania, gdy zamiast płynnej animacji pojawiają się zacięcia. Zacinanie występuje, gdy ruch zatrzymuje się na 1 lub więcej klatek, 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. Standardowa częstotliwość renderowania to 60 Hz, ale wiele nowszych urządzeń działa w trybie 90 Hz podczas interakcji użytkownika, np. podczas przewijania. Niektóre urządzenia obsługują jeszcze wyższe częstotliwości odświeżania, nawet do 120 Hz.
Aby sprawdzić, z jaką częstotliwością odświeżania działa urządzenie w danym momencie, włącz nakładkę w sekcji Debugowanie, wybierając Opcje programisty > Pokazuj częstotliwość odświeżania.
- Przejścia, które nie są płynne
Jest to widoczne podczas interakcji, takich jak przełączanie kart lub wczytywanie nowego działania. Tego typu przejścia muszą być płynnymi animacjami i nie mogą zawierać opóźnień ani migotania obrazu.
- Niska efektywność zasilania
Praca zmniejsza poziom naładowania baterii, a niepotrzebna praca skraca jej żywotność.
Alokacje pamięci, które powstają podczas tworzenia nowych obiektów w kodzie, mogą być przyczyną znacznego obciążenia systemu. Wynika to z tego, że nie tylko samo przydzielanie pamięci wymaga wysiłku ze strony środowiska wykonawczego Androida (ART), ale także późniejsze zwalnianie tych obiektów (odśmiecanie) wymaga czasu i wysiłku. Zarówno przydzielanie, jak i zbieranie pamięci jest znacznie szybsze i wydajniejsze, szczególnie w przypadku obiektów tymczasowych. Chociaż kiedyś zalecaliśmy unikanie przydzielania obiektów, gdy tylko jest to możliwe, teraz rekomendujemy, aby robić to, co ma sens w przypadku Twojej aplikacji i architektury. Oszczędzanie na alokacjach kosztem kodu, którego nie da się utrzymać, nie jest najlepszym rozwiązaniem, biorąc pod uwagę możliwości ART.
Wymaga to jednak wysiłku, więc pamiętaj, że jeśli w pętli wewnętrznej przydzielasz wiele obiektów, może to przyczynić się do problemów z wydajnością.
Identyfikowanie problemów
Aby zidentyfikować i rozwiązać problemy z wydajnością, zalecamy ten przepływ pracy:
- Zidentyfikuj i sprawdź te główne ścieżki użytkownika:
- Typowe procesy uruchamiania, w tym z launchera i powiadomienia.
- ekrany, na których użytkownik przewija dane;
- przejścia między ekranami,
- Długotrwałe procesy, takie jak nawigacja czy odtwarzanie muzyki.
- Sprawdź, co się dzieje w przypadku poprzednich przepływów, korzystając z tych narzędzi do debugowania:
- Perfetto: umożliwia sprawdzenie, co dzieje się na całym urządzeniu, z dokładnymi danymi o czasie.
- Profiler pamięci: umożliwia sprawdzenie, jakie alokacje pamięci są wykonywane w stercie.
- Simpleperf: pokazuje wykres płomieniowy wywołań funkcji, które w określonym czasie wykorzystują najwięcej procesora. Jeśli w Systrace znajdziesz coś, co trwa zbyt długo, ale nie wiesz dlaczego, Simpleperf może dostarczyć dodatkowych informacji.
Aby zrozumieć i rozwiązać te problemy z wydajnością, musisz ręcznie debugować poszczególne przebiegi testów. Nie możesz zastąpić poprzednich kroków analizą danych zbiorczych. Aby jednak dowiedzieć się, co widzą użytkownicy, i określić, kiedy mogą wystąpić regresje, ważne jest skonfigurowanie zbierania danych w testach automatycznych i w terenie:
- Procesy uruchamiania
- Dane z terenu: czas uruchamiania Konsoli Play
- Testy laboratoryjne: testowanie uruchamiania za pomocą Macrobenchmark
- Jank
- Zgromadzone dane
- 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 problemy z płynnością w całej aplikacji.
- Pomiary niestandardowe za pomocą
FrameMetricsAggregator
: możesz używaćFrameMetricsAggregator
do rejestrowania danych dotyczących zacięć podczas określonego przepływu pracy.
- Testy laboratoryjne
- Przewijanie za pomocą Macrobenchmark
- Testy porównawcze makro zbierają dane o czasie wyświetlania klatek za pomocą
dumpsys gfxinfo
poleceń ograniczających pojedynczą ścieżkę użytkownika. Pozwala to zrozumieć różnice w zacinaniu się animacji na określonej ścieżce użytkownika. WskaźnikiRenderTime
, które pokazują, jak długo trwa rysowanie klatek, są ważniejsze niż liczba niestabilnych klatek przy identyfikowaniu regresji lub ulepszeń.
- Zgromadzone dane
Problemy z weryfikacją linków aplikacji
Linki aplikacji to precyzyjne linki oparte na adresie URL Twojej witryny, które zostały zweryfikowane jako przynależne do Twojej witryny. Oto przyczyny, dla których weryfikacja linków do aplikacji może się nie powieść.
- Zakresy filtrów intencji: dodawaj tag
autoVerify
tylko do filtrów intencji w przypadku adresów URL, na które Twoja aplikacja może odpowiadać. - Niezweryfikowane przełączanie protokołów: niezweryfikowane przekierowania po stronie serwera i przekierowania do subdomeny
są uznawane za zagrożenie dla bezpieczeństwa i nie przechodzą weryfikacji. Powodują one, że wszystkie linki
autoVerify
nie działają. Na przykład przekierowanie linków z HTTP do HTTPS, np. z example.com na www.example.com, bez weryfikacji linków HTTPS może spowodować niepowodzenie weryfikacji. Pamiętaj, aby zweryfikować linki do aplikacji, dodając filtry intencji. - Nie można zweryfikować linków: dodanie linków, których nie można zweryfikować, do celów testowych może spowodować, że system nie zweryfikuje linków aplikacji w Twojej aplikacji.
- Niezawodne serwery: sprawdź, czy serwery mogą łączyć się z aplikacjami klienckimi.
Konfigurowanie aplikacji pod kątem analizy wydajności
Aby uzyskać dokładne, powtarzalne i przydatne w praktyce wyniki testów porównawczych aplikacji, musisz ją prawidłowo skonfigurować. Testuj na systemie jak najbardziej zbliżonym do produkcyjnego, eliminując źródła zakłóceń. W sekcjach poniżej znajdziesz kilka kroków związanych z plikami APK i systemem, które możesz wykonać, aby przygotować konfigurację testową. Niektóre z nich są specyficzne dla danego przypadku użycia.
Punkty śledzenia
Aplikacje mogą instrumentować swój kod za pomocą niestandardowych zdarzeń śledzenia.
Podczas rejestrowania śladów śledzenie powoduje niewielki narzut wynoszący około 5 μs na sekcję, więc nie umieszczaj go w każdej metodzie. Śledzenie większych fragmentów pracy trwających ponad 0,1 ms może dostarczyć istotnych informacji o wąskich gardłach.
Uwagi dotyczące pliku APK
Wersje debugowania mogą być przydatne podczas rozwiązywania problemów i symbolizowania próbek stosu, ale mają poważny wpływ na wydajność. Urządzenia z Androidem 10 (poziom interfejsu API 29) lub nowszym mogą używać w pliku manifestu elementu profileable android:shell="true"
, aby włączyć profilowanie w wersjach produkcyjnych.
Użyj konfiguracji zmniejszania kodu w wersji produkcyjnej. W zależności od zasobów używanych przez aplikację może to mieć znaczący wpływ na wydajność. Niektóre konfiguracje ProGuard usuwają punkty śledzenia, więc rozważ usunięcie tych reguł z konfiguracji, na której przeprowadzane są testy.
Kompilacja
Skompiluj aplikację na urządzeniu do znanego stanu – zwykle speed
dla uproszczenia lub speed-profile
, aby dokładniej dopasować wydajność do wersji produkcyjnej (wymaga to jednak rozgrzania aplikacji i zrzucenia profili lub skompilowania profili bazowych aplikacji).
Zarówno speed
, jak i speed-profile
zmniejszają ilość kodu wykonywanego z pliku DEX, a co za tym idzie, ilość kompilacji JIT w tle, która może powodować znaczne zakłócenia. Tylko speed-profile
zmniejsza wpływ wczytywania klas w czasie działania z pliku DEX.
To polecenie kompiluje aplikację w trybie speed
:
adb shell cmd package compile -m speed -f com.example.packagename
Tryb kompilacji speed
kompiluje metody aplikacji w całości. Tryb speed-profile
kompiluje metody i klasy aplikacji zgodnie z profilem wykorzystywanych ścieżek kodu, który jest zbierany podczas korzystania z aplikacji. Konsekwentne i prawidłowe zbieranie profili może być trudne, więc jeśli zdecydujesz się ich używać, sprawdź, czy zbierają one to, czego oczekujesz. Profile znajdują się w tej lokalizacji:
/data/misc/profiles/ref/[package-name]/primary.prof
Względy systemowe
Aby uzyskać pomiary o niskiej i wysokiej wierności, skalibruj urządzenia. Przeprowadzaj porównania A/B na tym samym urządzeniu i w tej samej wersji systemu operacyjnego. Wydajność może się znacznie różnić nawet w przypadku tego samego typu urządzenia.
Na urządzeniach z dostępem do roota możesz użyć lockClocks
skryptu do testów porównawczych. Skrypty te wykonują między innymi te czynności:
- Ustaw stałą częstotliwość procesorów.
- Wyłącz małe rdzenie i skonfiguruj procesor graficzny.
- Wyłącz ograniczanie termiczne.
Nie zalecamy używania skryptu lockClocks
w przypadku testów skupionych na wrażeniach użytkowników, takich jak testowanie uruchamiania aplikacji, testowanie dziennego użytkowania i testowanie zacinania się, ale może on być niezbędny do zmniejszenia szumu w testach mikroporównawczych.
W miarę możliwości używaj platformy testowej, np. Macrobenchmark, która może zmniejszyć szum w pomiarach i zapobiec niedokładności pomiarów.
Powolne uruchamianie aplikacji: niepotrzebna aktywność trampoliny
Aktywność trampoliny może niepotrzebnie wydłużać czas uruchamiania aplikacji, dlatego warto wiedzieć, czy Twoja aplikacja to robi. Jak widać na poniższym przykładzie śladu, jeden znak activityStart
jest natychmiast poprzedzony przez kolejny znak activityStart
bez rysowania klatek przez pierwszą aktywność.
Rysunek 1. Ślad pokazujący aktywność na trampolinie.
Może to wystąpić zarówno w przypadku punktu wejścia powiadomienia, jak i zwykłego punktu wejścia uruchamiania aplikacji. Często można to rozwiązać, refaktoryzując kod. Jeśli na przykład używasz tego działania do przeprowadzenia konfiguracji przed uruchomieniem innego działania, wyodrębnij ten kod do komponentu lub biblioteki wielokrotnego użytku.
Niepotrzebne przydziały powodujące częste odśmiecanie pamięci
W raporcie Systrace możesz zauważyć, że odśmiecanie pamięci (GC) odbywa się częściej, niż oczekujesz.
W przykładzie poniżej co 10 sekund podczas długotrwałej operacji pojawia się wskaźnik, że aplikacja może niepotrzebnie, ale konsekwentnie przydzielać pamięć:
Rysunek 2. Ślad pokazujący odstępy między zdarzeniami GC.
Możesz też zauważyć, że w profilerze pamięci większość alokacji jest wykonywana przez określony stos wywołań. Nie musisz agresywnie eliminować wszystkich alokacji, ponieważ może to utrudnić utrzymanie kodu. Zamiast tego zacznij od obszarów o największej liczbie przydziałów.
Nierówne klatki
Potok graficzny jest dość skomplikowany i może być trudno określić, czy użytkownik ostatecznie zobaczy pominiętą klatkę. W niektórych przypadkach platforma może „uratować” klatkę, korzystając z buforowania. Większość tych niuansów możesz jednak zignorować, aby z perspektywy aplikacji zidentyfikować problematyczne klatki.
Gdy klatki są rysowane przy niewielkim nakładzie pracy ze strony aplikacji, punkty śledzeniaChoreographer.doFrame()
występują co 16,7 ms na urządzeniu o częstotliwości odświeżania 60 klatek na sekundę:
Rysunek 3. Ślad pokazujący częste szybkie klatki.
Jeśli oddalisz widok i przejrzysz ślad, zobaczysz, że czasami ukończenie klatek zajmuje nieco więcej czasu, ale nadal jest to w porządku, ponieważ nie przekracza przydzielonego czasu 16,7 ms:
Rysunek 4. Ślad pokazujący częste szybkie klatki z okresowymi wybuchami pracy.
Gdy zauważysz zakłócenie tej regularnej częstotliwości, będzie to niestabilna klatka, jak pokazano na rysunku 5:
Rysunek 5. Ślad pokazujący zacinającą się klatkę.
Możesz ćwiczyć ich rozpoznawanie.
Rysunek 6. Ślad pokazujący więcej niestabilnych klatek.
W niektórych przypadkach musisz powiększyć punkt śledzenia, aby uzyskać więcej informacji o tym, które widoki są powiększane lub co robi RecyclerView
. W innych przypadkach może być konieczne dokładniejsze sprawdzenie.
Więcej informacji o identyfikowaniu niestabilnych klatek i debugowaniu ich przyczyn znajdziesz w sekcji Powolne renderowanie.
Typowe błędy w przypadku elementu RecyclerView
Unieważnianie wszystkich danych pomocniczych elementu RecyclerView
może niepotrzebnie prowadzić do długiego czasu renderowania klatek i zacinania się animacji. Zamiast tego, aby zminimalizować liczbę widoków, które wymagają aktualizacji, unieważniaj tylko dane, które uległy zmianie.
Więcej informacji o tym, jak uniknąć kosztownych wywołań notifyDatasetChanged()
, które powodują aktualizację treści zamiast ich całkowitego zastąpienia, znajdziesz w artykule Wyświetlanie danych dynamicznych.
Jeśli nie obsługujesz prawidłowo wszystkich zagnieżdżonych elementów RecyclerView
, może to spowodować całkowite odtworzenie wewnętrznego elementu RecyclerView
za każdym razem. Każdy zagnieżdżony, wewnętrzny element RecyclerView
musi mieć ustawiony element RecycledViewPool
, aby zapewnić możliwość ponownego wykorzystania widoków między poszczególnymi wewnętrznymi elementami RecyclerView
.
Jeśli nie pobierzesz wstępnie wystarczającej ilości danych lub nie zrobisz tego w odpowiednim czasie, dotarcie do końca listy przewijanej może być nieprzyjemne, gdy użytkownik musi czekać na więcej danych z serwera. Nie jest to co prawda zacinanie, ponieważ nie są przekraczane żadne terminy klatek, ale możesz znacznie poprawić UX, modyfikują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. Aby dowiedzieć się więcej o śledzeniu systemu i korzystaniu z profilera Androida Studio, obejrzyj ten film.
Debugowanie uruchamiania aplikacji za pomocą narzędzia Systrace
Więcej informacji o procesie uruchamiania aplikacji znajdziesz w sekcji Czas uruchomienia aplikacji, a omówienie śledzenia systemu znajdziesz w tym filmie:
Typy startupów możesz rozróżniać na tych etapach:
- Uruchomienie „na zimno”: rozpoczęcie tworzenia nowego procesu bez zapisanego stanu.
- Ciepłe uruchomienie: odtwarza aktywność, ponownie wykorzystując proces, lub odtwarza proces z zapisanym stanem.
- Szybkie uruchomienie: ponownie uruchamia aktywność i rozpoczyna ją od inflacji.
Zalecamy rejestrowanie śladów systemowych za pomocą aplikacji Śledzenie systemu na urządzeniu. W przypadku Androida 10 i nowszych wersji użyj Perfetto. W przypadku Androida 9 i starszych wersji użyj Systrace. Zalecamy też wyświetlanie plików śledzenia za pomocą internetowego narzędzia Perfetto. Więcej informacji znajdziesz w artykule Omówienie śledzenia systemu.
Oto kilka rzeczy, na które warto zwrócić uwagę:
- Monitorowanie rywalizacji: rywalizacja o zasoby chronione przez monitor może znacznie opóźnić uruchomienie aplikacji.
Transakcje synchroniczne w binderze: poszukaj niepotrzebnych transakcji na ścieżce krytycznej aplikacji. Jeśli niezbędna transakcja jest kosztowna, rozważ współpracę z zespołem platformy, aby wprowadzić ulepszenia.
Równoczesne odśmiecanie pamięci: jest to powszechne i ma stosunkowo niewielki wpływ, ale jeśli zdarza się często, warto przyjrzeć się temu problemowi za pomocą profilera pamięci w Android Studio.
Operacje wejścia-wyjścia: sprawdź operacje wejścia-wyjścia wykonywane podczas uruchamiania i poszukaj długich przestojów.
Znaczna aktywność w innych wątkach: może ona zakłócać działanie wątku interfejsu, dlatego podczas uruchamiania aplikacji należy uważać na pracę w tle.
Aby uzyskać lepsze raportowanie danych dotyczących uruchamiania aplikacji, zalecamy wywołanie funkcji reportFullyDrawn
po zakończeniu uruchamiania z perspektywy aplikacji. Więcej informacji o używaniu reportFullyDrawn
znajdziesz w sekcji Czas do pełnego wyświetlenia.
Zdefiniowane przez RFD czasy rozpoczęcia można wyodrębnić za pomocą procesora śladów Perfetto. Emitowane jest zdarzenie śledzenia widoczne dla użytkownika.
Korzystanie z narzędzia System Tracing na urządzeniu
Możesz użyć aplikacji na poziomie systemu o nazwie Śledzenie systemu, aby zarejestrować ślad systemu na urządzeniu. Ta aplikacja umożliwia rejestrowanie śladów z urządzenia bez konieczności podłączania go do adb
.
Korzystanie z Memory Profilera w Android Studio
Za pomocą profilera pamięci w Android Studio możesz sprawdzić obciążenie pamięci, które może być spowodowane wyciekami pamięci lub nieprawidłowymi wzorcami użycia. Umożliwia podgląd na żywo przydzielania obiektów.
Problemy z pamięcią w aplikacji możesz rozwiązać, korzystając z informacji z profilera pamięci, aby śledzić, dlaczego i jak często występuje odśmiecanie pamięci.
Aby profilować pamięć aplikacji, wykonaj te czynności:
wykrywać problemy z pamięcią;
Nagrywaj sesję profilowania pamięci ścieżki użytkownika, na której chcesz się skupić. Zwróć uwagę na rosnącą liczbę obiektów, jak pokazano na rysunku 7, co ostatecznie prowadzi do odzyskiwania pamięci, jak pokazano na rysunku 8.
Rysunek 7. Zwiększanie liczby obiektów.
Rysunek 8. czyszczenie pamięci,
Po zidentyfikowaniu ścieżki użytkownika, która powoduje obciążenie pamięci, przeanalizuj przyczyny źródłowe tego obciążenia.
diagnozować obszary o wysokim obciążeniu pamięci;
Wybierz zakres na osi czasu, aby wizualizować zarówno przydziały, jak i rozmiar płytki, jak pokazano na rysunku 9.
Rysunek 9. Wartości dla Allocations i Shallow Size.
Dane te można sortować na wiele sposobów. Oto kilka przykładów, jak poszczególne widoki mogą pomóc w analizowaniu problemów.
Uporządkuj według klasy: przydatne, gdy chcesz znaleźć klasy, które generują obiekty, które w innych przypadkach są buforowane lub ponownie wykorzystywane z puli pamięci.
Jeśli na przykład aplikacja tworzy co sekundę 2000 obiektów klasy „Vertex”, liczba przydziałów zwiększa się co sekundę o 2000. Możesz to zobaczyć, sortując według klasy. Jeśli chcesz ponownie użyć tych obiektów, aby uniknąć generowania nieużytków, zaimplementuj pulę pamięci.
Uporządkuj według stosu wywołań: przydatne, gdy chcesz znaleźć miejsce, w którym występuje ścieżka krytyczna, w której przydzielana jest pamięć, np. w pętli lub w określonej funkcji wykonującej dużo pracy związanej z przydzielaniem pamięci.
Shallow Size (Rozmiar płytki): śledzi tylko pamięć samego obiektu. Jest to przydatne w przypadku śledzenia prostych klas składających się głównie z wartości pierwotnych.
Rozmiar przechowywany: pokazuje łączną ilość pamięci zajmowaną przez obiekt i odwołania, które są używane tylko przez ten obiekt. Przydaje się to do śledzenia obciążenia pamięci spowodowanego złożonymi obiektami. Aby uzyskać tę wartość, wykonaj pełny zrzut pamięci, jak pokazano na rysunku 10. Następnie dodaj kolumnę Rozmiar zachowany, jak pokazano na rysunku 11.
Rysunek 10. Pełny zrzut pamięci.
Rysunek 11. kolumnie Zachowany rozmiar.
Pomiar wpływu optymalizacji.
GC są bardziej widoczne i łatwiej jest zmierzyć wpływ optymalizacji pamięci. Gdy optymalizacja zmniejsza obciążenie pamięci, widzisz mniej odzyskiwania pamięci.
Aby zmierzyć wpływ optymalizacji, na osi czasu profilera zmierz czas między odzyskiwaniem pamięci. Widać wtedy, że przerwy między kolejnymi odzyskiwaniami pamięci są dłuższe.
Ostateczne efekty ulepszeń pamięci są następujące:
- Jeśli aplikacja nie będzie stale wywierać presji na pamięć, prawdopodobieństwo wyłączenia z powodu braku pamięci będzie mniejsze.
- Mniejsza liczba odzyskiwania pamięci poprawia wskaźniki zacinania się, zwłaszcza w przypadku wartości P99. Dzieje się tak, ponieważ odśmiecanie pamięci powoduje rywalizację o procesor, co może prowadzić do odroczenia zadań renderowania podczas odśmiecania pamięci.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Analiza i optymalizacja uruchamiania aplikacji {:#app-startup-analysis-optimization}
- Zablokowane klatki
- Tworzenie testu Macrobenchmark