Omówienie pomiaru skuteczności aplikacji

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 dotyczących 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 wczytać 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 negatywnie wpływać na wrażenia 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 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. Tego typu przejścia 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 pamiętaj, że może to spowodować problemy z wydajnością, jeśli przydzielasz wiele obiektów w pętli wewnętrznej.

Identyfikowanie problemów

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

  1. 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.
  2. 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 wykryć, kiedy mogą wystąpić regresje, ważne jest skonfigurowanie zbierania danych w ramach testów automatycznych i w polu:

  • Sekwencje uruchamiania
  • 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ć funkcji FrameMetricsAggregator do rejestrowania danych o zakłóceniach podczas określonego procesu.
    • Testy laboratoryjne
      • Przewijanie za pomocą Macrobenchmark.
      • Test makrobenchmarku zbiera informacje o czasie trwania 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. Dane RenderTime, które wskazują, jak długo trwa renderowanie klatki, są ważniejsze niż liczba klatek z zakłóceniami w przypadku wykrywania regresji lub ulepszeń.

Linki aplikacji to precyzyjne linki oparte na adresie URL witryny, które zostały zweryfikowane jako należące do Twojej witryny. Oto kilka powodów, 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, np. 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.
  • Niewiarygodne serwery: upewnij się, że serwery mogą łączyć się z aplikacją klienta.

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 związanych z plikiem APK i systemem, które możesz wykonać, aby przygotować konfigurację testową. Niektóre z nich są związane z konkretnym przypadkiem użycia.

punkty kontrolne,

Aplikacje mogą wykorzystywać 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. Przechwytywanie 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-profilezmniejsza 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, który jest zbierany 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 tym miejscu:

/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.

alt_text Rysunek 1. Ślad przedstawiający aktywność na trampolinie.

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:

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

Możesz też zauważyć, że określony stos wywołania odpowiada za zdecydowaną większość alokacji podczas korzystania z profilowania pamięci. 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 śladuChoreographer.doFrame() występują co 16,7 ms na urządzeniu z częstotliwością 60 FPS:

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

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:

alt_text Rysunek 4. Ślad przedstawiający częste szybkie klatki z okresowymi seriami pracy.

Jeśli zauważysz przerwę w tym regularnym rytmie, oznacza to, że występuje klatka z niestabilnym obrazem, jak pokazano na rysunku 5:

alt_text Rysunek 5. Ślad pokazujący klatkę z zakłóceniem.

Możesz ćwiczyć ich rozpoznawanie.

alt_text Rysunek 6. Ślad przedstawiający więcej klatek z problemami.

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 niepłynnych klatek i debugowaniu ich przyczyn znajdziesz w artykule Wydajne 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 poziom RecyclerView musi mieć ustawioną wartość RecycledViewPool, aby zapewnić możliwość ponownego użycia widoków na każdym poziomie RecyclerView.

Niewystarczające wstępne pobieranie danych lub niewystarczające wstępne pobieranie danych w odpowiednim czasie może powodować, że przewijanie listy do dołu będzie 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ą narzędzia Systrace

Informacje o czasie uruchamiania aplikacji znajdziesz w artykule Czas uruchamiania aplikacji, a omówienie śledzenia systemu w tym filmie.

Typy uruchamiania można rozróżnić 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: powoduje ponowne uruchomienie aktywności i rozpoczęcie jej od momentu załadowania.

Zalecamy rejestrowanie zdarzeń Systray za pomocą aplikacji System Tracing na urządzeniu. W przypadku Androida 10 lub nowszego użyj 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 nie występują operacje wejścia-wyjścia, i sprawdź, czy nie 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. W tym celu emitowane jest 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żytkowania. 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:

  1. 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.

    alt_text Rysunek 7. Zwiększanie liczby obiektów.

    alt_text Rysunek 8. wywozu śmieci;

    Po zidentyfikowaniu ścieżki użytkownika, która zwiększa obciążenie pamięci, znajdź przyczyny tego obciążenia.

  2. diagnozować miejsca, w których występuje duże obciążenie pamięci;

    Wybierz zakres na osi czasu, aby wyświetlić zarówno przypisania, jak i mały rozmiar, jak pokazano na rysunku 9.

    alt_text Rysunek 9. Wartości w kolumnach PrzydziałyPłytka wielkość.

    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. widzisz, że aplikacja co sekundę tworzy 2000 obiektów klasy „Vertex”, to 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 niepotrzebnych danych, 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.

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

      Kolumna Zachowany rozmiar.
      Rysunek 11. Kolumna Zachowany rozmiar.
  3. 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 trwa to dłużej.

    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.