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ą 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:

  1. 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.
  2. 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
  • 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. Dane RenderTime, które określają czas renderowania długich klatek, są ważniejsze przy identyfikowaniu regresji lub ulepszeń niż liczba zbędnych klatek.

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.

tekst_alternatywny 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 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:

tekst_alternatywny Rysunek 2. Zrzut przedstawiający odstęp między zdarzeniami GC.

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:

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

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:

tekst_alternatywny Rysunek 4. Ślad pokazujący częste klatki z okresowymi seriami zadań.

Zakłócenie tego standardowego rytmu jest spowodowane nieregularną ramką, jak widać na rysunku 5:

tekst_alternatywny Rysunek 5. Śledzenie z nieprawidłową ramką.

Możesz przećwiczyć ich rozpoznawanie.

tekst_alternatywny Rysunek 6. Zrzut przedstawiający więcej niedziałających klatek.

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:

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

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

    tekst_alternatywny Rysunek 8. Usuwanie odpadów.

    Po zidentyfikowaniu ścieżki użytkownika, która zwiększa wykorzystanie pamięci, przeanalizuj główne przyczyny takiego obciążenia.

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

    tekst_alternatywny Rysunek 9. Wartości Przydziałów i Płytki rozmiar.

    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.

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

      Kolumna Zachowany rozmiar.
      Rysunek 11. Kolumna Zachowany rozmiar.
  3. 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.