Omówienie pomiaru skuteczności aplikacji

Ten dokument pomoże Ci zidentyfikować i rozwiązać kluczowe problemy z wydajnością aplikacji.

Najważniejsze problemy ze skutecznością

Może być wiele problemów, które mogą wpływać na wydajność aplikacji, ale warto zwrócić uwagę na te typowe:

Czas oczekiwania na uruchomienie

Czas oczekiwania na uruchomienie to czas między kliknięciem ikony aplikacji, powiadomienia lub innego punktu wejścia do wyświetlenia danych użytkownika na ekranie.

Zadbaj o to, aby Twoje aplikacje były zgodne z tymi celami:

  • Uruchomienie „na zimno” w czasie krótszym niż 500 ms. Uruchomienie „na zimno” ma miejsce, gdy uruchamiana aplikacja nie jest zapisana w pamięci systemu. Dzieje się tak, gdy aplikacja jest uruchamiana po raz pierwszy od ponownego uruchomienia lub gdy proces aplikacji został zatrzymany przez użytkownika albo system.

    Uruchomienie częściowo z pamięci ma miejsce wtedy, gdy aplikacja już działa w tle. Uruchomienie „na zimno” wymaga najwięcej pracy ze strony systemu, ponieważ musi załadować wszystko z pamięci i zainicjować aplikację. Staraj się uruchamiać aplikacje „na zimno” nie dłużej niż 500 ms.

  • Czasy oczekiwania P95 i P99 są bardzo zbliżone do mediany czasu oczekiwania. Jeśli uruchomienie aplikacji trwa bardzo długo, pogarsza to wrażenia użytkowników. Komunikacja międzyprocesowa (IPC) i niepotrzebne operacje wejścia-wyjścia podczas krytycznej ścieżki uruchamiania aplikacji mogą powodować rywalizację o blokadę i powodować niespójności.

Zacinanie się

Jank to termin opisujący wizualne zakłócenia, które występują, gdy system nie jest w stanie zbudować danych i dostarczyć klatki na czas, aby wyświetlić je na ekranie z żądaną częstotliwością wynoszącą 60 Hz lub większą. Szum jest najbardziej widoczny przy przewijaniu, gdy zamiast płynnego animacji pojawiają się czkawki. Pojawia się, gdy ruch zatrzymuje się po drodze na co najmniej jedną klatkę, ponieważ renderowanie treści przez aplikację trwa dłużej niż czas trwania klatki w systemie.

Aplikacje muszą mieć częstotliwość odświeżania 90 Hz. Konwencjonalne współczynniki renderowania to 60 Hz, ale wiele nowszych urządzeń działa w trybie 90 Hz podczas interakcji użytkownika, takich jak przewijanie. Niektóre urządzenia obsługują jeszcze wyższą częstotliwość, nawet do 120 Hz.

Aby sprawdzić, jaka częstotliwość odświeżania używa w danym momencie urządzenie, włącz nakładkę, w sekcji Debugowanie wybierz kolejno Opcje programisty > Pokaż częstotliwość odświeżania.

Nie płynne przejścia

Jest to widoczne podczas interakcji, takich jak przełączanie się między kartami lub wczytywanie nowej aktywności. Tego rodzaju przejścia muszą być płynnymi animacjami i nie mogą zawierać opóźnień ani migotania.

Nieefektywność energii

Praca zmniejsza ładowanie baterii, a wykonywanie niepotrzebnych czynności skraca jej żywotność.

Przydziały pamięci, które powstają w wyniku tworzenia nowych obiektów w kodzie, mogą być przyczyną dużej ilości pracy w systemie. Dzieje się tak, ponieważ nie tylko same przydziały wymagają działania ze środowiska wykonawczego Androida (ART), ale zwolnienie tych obiektów później (odbieranie odpadów) również wymaga czasu i wysiłku. Alokacja i zbieranie danych działają znacznie szybciej i wydajniej, zwłaszcza w przypadku obiektów tymczasowych. Sprawdzoną metodą było unikanie przydzielania obiektów tam, gdzie jest to możliwe, ale zalecamy, aby robić to, co najlepiej sprawdza się w przypadku aplikacji i architektury. Oszczędności w przydziałach, które wiążą się z niewykorzystaniem kodu, nie są najlepszą metodą, biorąc pod uwagę możliwości ART.

Wymaga to jednak pewnego wysiłku, więc jeśli przydzielasz wiele obiektów w wewnętrznej pętli, może to powodować problemy z wydajnością.

Zidentyfikuj problemy

Aby wykryć i rozwiązać problemy z wydajnością, zalecamy wykonanie tych czynności:

  1. Określ i przeanalizuj te najważniejsze ścieżki użytkowników:
    • 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.
  2. Sprawdź, co dzieje się w poprzednich przepływach, korzystając z tych narzędzi do debugowania:
    • Perfetto: umożliwia sprawdzanie, co dzieje się na całym urządzeniu, dzięki dokładnym danym o czasie.
    • Program do profilowania pamięci: umożliwia sprawdzanie, jakie alokacje pamięci odbywają się na stercie.
    • Simpleperf: pokazuje wykres płomowy pokazujący, które wywołania funkcji najbardziej obciążają procesor w określonym czasie. Jeśli wykryjesz coś, co w Systrace zajmuje Ci dużo czasu, ale nie wiesz, dlaczego, Simpleperf może podać Ci dodatkowe informacje.

Aby zrozumieć i debugować te problemy z wydajnością, konieczne 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, trzeba skonfigurować zbieranie danych w ramach testów automatycznych oraz w polu:

  • Procesy uruchamiania
  • Jank
    • Dane pól
      • Podstawowe wskaźniki ramowe w Konsoli Play: w Konsoli Play nie można zawężać danych do konkretnej ścieżki użytkownika. Informuje tylko o ogólnym zacięciu w aplikacji.
      • Pomiar niestandardowy za pomocą narzędzia FrameMetricsAggregator: za pomocą metody FrameMetricsAggregator możesz rejestrować dane dotyczące zacięć w obrębie określonego przepływu pracy.
    • Testy laboratoryjne
      • Przewijanie z użyciem Macroporównania.
      • Macroporównanie zbiera czas klatek za pomocą poleceń dumpsys gfxinfo, które obejmują ścieżkę pojedynczego użytkownika. To sposób na zrozumienie zmienności zacięć na konkretnej ścieżce użytkownika. Dane RenderTime, które pokazują, jak długo trwa rysowanie klatek, są ważniejsze niż liczba nieregularnych klatek do identyfikowania regresji lub ulepszeń.

Linki do aplikacji to precyzyjne linki utworzone na podstawie adresu URL Twojej witryny, które zostały zweryfikowane jako należące do Twojej witryny. Poniżej podajemy przyczyny niepowodzenia weryfikacji linku aplikacji.

  • Zakresy filtrów intencji: dodaj tylko autoVerify do filtrów intencji w przypadku adresów URL, na które Twoja aplikacja może reagować.
  • Zmiana protokołu niezweryfikowanego: niezweryfikowane przekierowania po stronie serwera i subdomeny są uznawane za zagrożenia dla bezpieczeństwa i nieudane weryfikację. Sprawia to, że wszystkie linki autoVerify zakończą się niepowodzeniem. Na przykład przekierowanie linków z HTTP do HTTPS, np. example.com, na www.example.com, bez weryfikacji linków HTTPS może spowodować, że weryfikacja się nie powiedzie. Pamiętaj, aby zweryfikować linki aplikacji, dodając filtry intencji.
  • Linki nieweryfikowalne: dodanie nieweryfikowalnych linków do celów testowych może spowodować, że system nie będzie weryfikować linków aplikacji w Twojej aplikacji.
  • Niestabilne serwery: sprawdź, czy Twoje serwery mogą łączyć się z aplikacjami klienckimi.

Konfigurowanie aplikacji do analizy wydajności

Prawidłowa konfiguracja pozwala uzyskiwać dokładne, powtarzalne i przydatne testy porównawcze z aplikacji. Testuj testy w systemie, który jest jak najbardziej zbliżony do wersji produkcyjnej, a jednocześnie eliminuje źródła szumu. W sekcjach poniżej znajdziesz opis czynności, które możesz wykonać w poszczególnych plikach APK i systemie, aby przygotować konfigurację testu. 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 przechwytywania śladów śledzenie wymaga niewielkich nakładów pracy wynoszących około 5 μs na sekcję, dlatego nie należy stosować go w każdej metodzie. Śledzenie większych fragmentów pracy trwających dłużej niż 0,1 ms może dać nam wgląd w wąskie gardła.

Uwagi na temat pakietu 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ą używać usługi profileable android:shell="true" w swoim pliku manifestu, aby włączyć profilowanie w kompilacjach wersji.

Użyj konfiguracji zmniejszania kodu klasy produkcyjnej. W zależności 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ł z konfiguracji, na której testujesz.

Kompilacja

Skompiluj aplikację na urządzeniu do znanego stanu – zwykle speed lub speed-profile. Aktywność w tle tylko w czasie (JIT) może powodować większe obciążenie wydajności i często się tym zdarza, jeśli ponownie instalujesz plik APK między kolejnymi testami. Oto polecenie, które pozwala wykonać te czynności:

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 używanych ścieżek kodu, które są zbierane podczas korzystania z aplikacji. Spójne i prawidłowe zbieranie profili może być trudne, dlatego jeśli zdecydujesz się ich używać, sprawdź, czy gromadzą to, czego oczekujesz. Profile znajdują się w tej lokalizacji:

/data/misc/profiles/ref/[package-name]/primary.prof

Analiza porównawcza w skali makro umożliwia bezpośrednie określanie trybu kompilacji.

Uwagi systemowe

Aby dokonywać pomiarów niskiego i dużej dokładności, skalibruj urządzenia. Porównania testów A/B na tym samym urządzeniu i w tej samej wersji systemu operacyjnego. Nawet w przypadku różnych urządzeń tego samego typu mogą występować znaczne różnice w skuteczności.

Na urządzeniach z dostępem do roota możesz użyć skryptu lockClocks do przeprowadzania mikroporównań. Skrypty te wykonują między innymi te działania:

  • Umieść procesory o stałej częstotliwości.
  • Wyłącz małe rdzenie i skonfiguruj GPU.
  • Wyłącz ograniczanie termiczne.

Nie zalecamy korzystania ze skryptu lockClocks w przypadku ukierunkowanych na użytkownika testów, takich jak uruchamianie aplikacji, testowanie DoU czy testowanie zacięć, ale może to być konieczne do zredukowania szumu w testach mikroporównawczych.

W miarę możliwości warto korzystać z platformy do testowania, takiej jak Macroporównania, która może ograniczać szum w pomiarach i zapobiegać niedokładnościom.

Powolne uruchamianie aplikacji: niepotrzebna aktywność na trampolinie

Ćwiczenie na trampolinie może niepotrzebnie wydłużyć czas uruchamiania aplikacji. Dlatego warto o tym pamiętać, jeśli robi to Twoja aplikacja. Jak widać w poniższym przykładzie, po 1 elemencie activityStart następuje natychmiast inny element activityStart, a pierwsza aktywność nie pobiera żadnych klatek.

tekst_alternatywny Rysunek 1. Ślad pokazujący aktywność na trampolinie.

Może się to zdarzyć zarówno w punkcie wejścia powiadomienia, jak i w normalnym punkcie wejścia aplikacji. Często można rozwiązać ten problem przez refaktoryzację. Jeśli np. używasz tego działania do konfiguracji przed uruchomieniem innego działania, umieść ten kod w komponencie lub bibliotece wielokrotnego użytku.

Niepotrzebne przydziały wyzwalające częste GC

Może się zdarzyć, że proces czyszczenia pamięci (GC) odbywa się częściej niż jest to możliwe w przypadku Systrace.

W tym przykładzie każde 10 sekund podczas długo trwającej operacji wskazuje, że aplikacja może przydzielać niepotrzebnie i spójnie w czasie:

tekst_alternatywny Rysunek 2. Ślad pokazujący odstęp między zdarzeniami GC.

Możesz też zauważyć, że podczas korzystania z programu profilującego pamięci za większość przydziałów odpowiada określony stos wywołań. Nie musisz intensywnie eliminować wszystkich przydziałów, ponieważ może to utrudniać obsługę kodu. Zacznij od pracy nad hotspotami alokacji.

Nieregularne ramki

Potok graficzny jest stosunkowo skomplikowany, a przy podejmowaniu decyzji, czy użytkownik zobaczy pominiętą klatkę, mogą wystąpić pewne niuanse. W niektórych przypadkach platforma może „uratować” klatkę za pomocą buforowania. Możesz jednak zignorować większość tych niuansów, aby zidentyfikować problematyczne ramki z perspektywy aplikacji.

Gdy klatki są rysowane bez nakładu pracy wymaganej przez aplikację, punkty śledzenia Choreographer.doFrame() występują z częstotliwością 16,7 ms na urządzeniu z prędkością 60 FPS:

tekst_alternatywny Rysunek 3. Ślad pokazujący częste szybkie klatki.

Jeśli pomniejszysz obraz i nawigujesz po nim, czasami zobaczysz, że przetwarzanie klatek zajmuje trochę więcej czasu, ale i tak nie jest to zajęte, bo nie zajmuje to więcej niż 16,7 ms:

tekst_alternatywny Rysunek 4. Ślad pokazujący częste szybkie klatki z okresowymi sekwencjami pracy.

Jeśli zauważysz zakłócenie w tym regularnym cyklu, jest to zaburzona klatka, jak widać na ilustracji 5:

tekst_alternatywny Rysunek 5. Ślad pokazujący nieregularną ramkę.

Możesz przećwiczyć rozpoznawanie takich osób.

tekst_alternatywny Rysunek 6. Ślad pokazujący więcej nieregularnych klatek.

W niektórych przypadkach trzeba powiększyć punkt śledzenia, aby uzyskać więcej informacji o tym, które widoki są zawyżone lub co robi RecyclerView. W innych przypadkach konieczne może być dokładniejsze zbadanie sprawy.

Więcej informacji o identyfikowaniu nieregularnych ramek i debugowaniu ich przyczyn znajdziesz w artykule Powolne renderowanie.

Typowe błędy w RecyclerView

Niepotrzebne unieważnienie całych danych kopii zapasowej RecyclerView może prowadzić do długiego czasu renderowania klatek i zacinania się. Aby zminimalizować liczbę widoków danych, które wymagają aktualizacji, unieważnij tylko te dane, które się zmieniają.

W artykule Prezentowanie danych dynamicznych znajdziesz sposoby unikania kosztownych wywołań funkcji notifyDatasetChanged(), które powodują aktualizowanie treści, a nie jej całkowite zastępowanie.

Jeśli nie obsługujesz prawidłowo każdego zagnieżdżonego komponentu RecyclerView, może to spowodować, że za każdym razem całkowicie odtworzony wewnętrzny RecyclerView. Każdy zagnieżdżony wewnętrzny element RecyclerView musi mieć ustawiony parametr RecycledViewPool, aby umożliwić ponowne używanie widoków danych między każdym wewnętrznym elementem RecyclerView.

Niewystarczające pobieranie danych z wyprzedzeniem lub niewystarczające pobieranie z wyprzedzeniem w odpowiednim czasie może spowodować, że osiągnięcie końca przewijanej listy będzie irytujące, gdy użytkownik będzie musiał czekać na więcej danych z serwera. Chociaż technicznie nie jest to zawiłe, ponieważ nie zostają pominięte żadne terminy wyświetlania klatek, możesz znacznie poprawić wygodę użytkowników, zmieniając czas i ilość pobierania z wyprzedzeniem, aby użytkownik nie musiał czekać na dane.

Debugowanie aplikacji

Poniżej znajdziesz różne metody debugowania wydajności aplikacji. Obejrzyj film poniżej, aby dowiedzieć się więcej o śledzeniu systemu i narzędziu do profilowania w Android Studio.

Debugowanie uruchamiania aplikacji za pomocą Systrace

Omówienie procesu uruchamiania aplikacji znajdziesz w artykule Czas uruchomienia aplikacji, a omówione śledzenie systemu znajdziesz w tym filmie.

Typy startupów możesz rozróżniać na tych etapach:

  • Uruchomienie „na zimno”: zacznij od utworzenia nowego procesu bez zapisanego stanu.
  • Uruchomienie z pamięci: odtwarza aktywność jeszcze raz przy użyciu procesu lub tworzy proces z zapisanym stanem.
  • Uruchomienie z pamięci: ponownie uruchamia aktywność i rozpoczyna od inflacji.

Zalecamy przechwytywanie plików Systraces za pomocą aplikacji Śledzenie systemu na urządzeniu. W Androidzie 10 i nowszych użyj Perfetto. W przypadku Androida 9 i starszych wersji użyj Systrace. Zalecamy też wyświetlanie plików śledzenia w internetowej przeglądarce logów Perfetto. Więcej informacji znajdziesz w artykule Omówienie śledzenia systemu.

Oto kilka rzeczy, na które warto zwrócić uwagę:

  • Monitorowanie rywalizacji: konkurencja o zasoby chronione przez monitorowanie może spowodować znaczne opóźnienie w uruchamianiu aplikacji.
  • Synchroniczne transakcje binarne: poszukaj niepotrzebnych transakcji na ścieżce krytycznej aplikacji. Jeśli niezbędna transakcja jest kosztowna, rozważ wprowadzenie ulepszeń z pomocą odpowiedniego zespołu ds. platformy.

  • Równoczesne działanie GC: jest to powszechny i stosunkowo niewielki wpływ, ale jeśli zdarza się to często, warto przyjrzeć się temu za pomocą profilu pamięci w Android Studio.

  • Wejście-wyjście: sprawdzanie wejścia/wyjścia przeprowadzonego podczas uruchamiania i szukanie długich przerw.

  • Znaczna aktywność w innych wątkach: mogą one zakłócić działanie wątku UI, więc podczas uruchamiania zwróć uwagę na działanie w tle.

Aby usprawnić raportowanie danych o uruchamianiu aplikacji, zalecamy wywołanie metody reportFullyDrawn po ukończeniu uruchamiania z perspektywy aplikacji. Więcej informacji o korzystaniu z reportFullyDrawn znajdziesz w sekcji Czas do pełnego wyświetlenia. Zdefiniowane przez RFD czasy rozpoczęcia możesz wyodrębnić za pomocą procesora śledzenia Perfetto i wysyłane jest widoczne dla użytkownika zdarzenie logu czasu.

Użyj śledzenia systemu na urządzeniu

Do przechwycenia śladu systemu na urządzeniu możesz użyć dostępnej na poziomie systemu aplikacji o nazwie Śledzenie systemu. Ta aplikacja umożliwia rejestrowanie logów czasu z urządzenia bez konieczności podłączania go do zasilania lub łączenia z adb.

Użyj narzędzia do profilowania pamięci w Android Studio

Za pomocą narzędzia do profilowania pamięci w Android Studio możesz sprawdzić wykorzystanie pamięci, które może być spowodowane wyciekami pamięci lub nieprawidłowymi wzorcami jej wykorzystania. Udostępnia widok przydziałów obiektów na żywo.

Możesz rozwiązać problemy z pamięcią w aplikacji, korzystając z narzędzia do profilowania pamięci, aby śledzić, dlaczego i jak często występują tego typu ataki.

Aby profilować pamięć aplikacji, wykonaj te czynności:

  1. wykrywać 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, która ostatecznie prowadzi do GC, jak widać na rys. 8.

    tekst_alternatywny Rysunek 7. Zwiększam liczbę obiektów.

    tekst_alternatywny Rysunek 8. Gromadzenie odpadów.

    Gdy ustalisz, że podróż użytkownika zwiększa zużycie pamięci, przeanalizuj główne przyczyny nadmiernego obciążenia pamięci.

  2. Diagnozowanie obszarów wymagających wykorzystania pamięci.

    Wybierz zakres na osi czasu, aby wizualizować wartości Allocations (Przydziały) i Shallow size (Płytki rozmiar), jak pokazano na ilustracji 9.

    tekst_alternatywny Rysunek 9. Wartości Allocations i Shallow size.

    Dane te możesz sortować na kilka sposobów. Poniżej pokazujemy kilka przykładów, jak każdy widok może pomóc w analizie problemów.

    • Rozmieść według klasy: ta opcja jest przydatna, gdy chcesz znaleźć klasy generujące obiekty, które są przechowywane w pamięci podręcznej lub ponownie wykorzystywane z puli pamięci.

      Jeśli na przykład zobaczysz, że aplikacja tworzy 2000 obiektów klasy o nazwie „Vertex” co sekundę, liczba Allocations zwiększa się o 2000 co sekundę i jest widoczna podczas sortowania według klasy. Jeśli chcesz ponownie używać tych obiektów, aby uniknąć odśmiecania, zaimplementuj pulę pamięci.

    • Rozmieść według stosu wywołań: przydatna, gdy chcesz znaleźć miejsce, w którym jest przydzielana pamięć, np. w pętli lub w konkretnej funkcji wykonującej dużo zadań alokacji.

    • Shallow size (Rozmiar płytki): śledzi tylko pamięć samego obiektu. Jest on przydatny do śledzenia prostych klas złożonych głównie z wartości podstawowych.

    • Przechowywany rozmiar: pokazuje łączną ilość pamięci przypisanej do obiektu oraz odwołania, do których odwołuje się tylko ten obiekt. Jest przydatna do śledzenia obciążenia pamięci związanego ze złożonymi obiektami. Aby uzyskać tę wartość, wykonaj pełny zrzut pamięci, jak pokazano na rys. 10, a Zachowany rozmiar został dodany jako kolumna, jak widać na ilustracji 11.

      tekst_alternatywny Rysunek 10. Pełny zrzut pamięci.

      Zachowany rozmiar.
      Rysunek 11. Zachowany rozmiar.
  3. Pomiar wpływu optymalizacji.

    Użycie pamięci jest bardziej widoczne i łatwiejsze do zmierzenia wpływu optymalizacji pamięci. Gdy optymalizacja zmniejsza wykorzystanie pamięci, zmniejsza się ilość pamięci GC.

    Aby zmierzyć wpływ optymalizacji, na osi czasu programu profilującego mierz czas między GC. Dzięki temu zauważysz, że czas między GC jest dłuższy.

    Najważniejsze korzyści z poprawy pamięci:

    • Wyłączenia z powodu braku pamięci są prawdopodobnie zredukowane, jeśli aplikacja nie stale zwiększa wykorzystanie pamięci.
    • Mniejsza liczba GC oznacza lepsze wskaźniki zacięć, zwłaszcza w P99. Dzieje się tak, ponieważ algorytmy GC powodują rywalizację z CPU, co może doprowadzić do wstrzymywania zadań renderowania podczas ich wykonywania.