Przejdź do treści głównej
Wersja: 0.80

Profilowanie

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Profilowanie to proces analizowania wydajności aplikacji, zużycia zasobów i zachowania w celu zidentyfikowania potencjalnych wąskich gardeł lub nieefektywności. Warto korzystać z narzędzi profilujących, aby zapewnić płynne działanie aplikacji na różnych urządzeniach i w różnych warunkach.

Dla iOS nieocenionym narzędziem jest Instruments, a na Androidzie warto nauczyć się korzystać z profiler w Android Studio.

Ale najpierw upewnij się, że tryb deweloperski jest WYŁĄCZONY!.

Profilowanie wydajności interfejsu Android za pomocą System Tracing

Android obsługuje ponad 10 000 różnych telefonów i jest zaprojektowany do obsługi renderowania programowego: architektura frameworka i konieczność obsługi wielu konfiguracji sprzętowych oznacza, że w porównaniu z iOS dostajemy mniej "gotowych rozwiązań". Ale czasem można coś poprawić - i często wcale nie jest to wina natywnego kodu!

Pierwszym krokiem w debugowaniu zacinania się jest odpowiedź na kluczowe pytanie: gdzie spędzany jest czas podczas każdej 16-milisekundowej klatki. Do tego użyjemy wbudowanego profilera System Tracing w Android Studio.

Uwaga

Samodzielne narzędzie systrace zostało usunięte z pakietu platform-tools Androida. Należy zamiast tego użyć profilera Android Studio, który zapewnia tę samą funkcjonalność z lepszym interfejsem użytkownika.

1. Zbieranie śladu

Najpierw podłącz przez USB urządzenie, na którym występuje problem. Otwórz folder android projektu w Android Studio, wybierz urządzenie w prawym górnym panelu i uruchom projekt w trybie profilowalnym.

Gdy aplikacja jest zbudowana w trybie profilowalnym i działa na urządzeniu, przejdź do momentu tuż przed akcją, którą chcesz profilować. Następnie uruchom zadanie "Capture System Activities" w panelu Profilera Android Studio.

Po rozpoczęciu zbierania śladu wykonaj animację lub interakcję. Następnie naciśnij "Stop recording". Możesz teraz przeanalizować ślad bezpośrednio w Android Studio. Alternatywnie, wybierz go w panelu "Past Recordings", naciśnij "Export recording" i otwórz w narzędziu takim jak Perfetto.

2. Analiza śladu

Po otwarciu śladu w Android Studio lub Perfetto powinieneś zobaczyć coś takiego:

Przykład

Wskazówka

Używaj klawiszy WASD do przesuwania widoku i powiększania.

Interfejs może się różnić, ale poniższe instrukcje mają zastosowanie niezależnie od używanego narzędzia.

Włącz podświetlanie VSync

Zaznacz to pole w prawym górnym rogu ekranu, aby uwidocznić granice 16-milisekundowych klatek:

Włącz podświetlanie VSync

Powinieneś zobaczyć paski jak na powyższym zrzucie. Jeśli ich nie widać, spróbuj profilować na innym urządzeniu: telefony Samsung znane są z problemów z wyświetlaniem VSync, podczas gdy seria Nexus jest ogólnie bardziej niezawodna.

3. Znajdź swój proces

Przewiń, aż zobaczysz (część) nazwy swojego pakietu. W tym przypadku profilowałem com.facebook.adsmanager, który wyświetla się jako book.adsmanager z powodu ograniczeń długości nazw wątków w jądrze.

Po lewej stronie zobaczysz listę wątków odpowiadających liniom czasu po prawej. Kilka wątków jest dla nas istotnych: wątek UI (z nazwą pakietu lub "UI Thread"), mqt_js oraz mqt_native_modules. Jeśli używasz Androida 5.0 lub nowszego, istotny jest też wątek renderowania (Render Thread).

  • Wątek interfejsu użytkownika (UI Thread). Tutaj odbywają się standardowe operacje mierzenia, układania i rysowania w Androidzie. Nazwa wątku po prawej będzie nazwą twojego pakietu (w moim przypadku book.adsmanager) lub "UI Thread". Zdarzenia na tym wątku powinny być związane z Choreographer, traversals i DispatchUI:

    Przykład wątku interfejsu użytkownika

  • Wątek JavaScript (JS Thread). Tutaj wykonywany jest kod JavaScript. Nazwa wątku to mqt_js lub <...> w zależności od współpracy jądra urządzenia. Jeśli nie ma nazwy, szukaj elementów jak JSCall, Bridge.executeJSCall itp:

    Przykład wątku JavaScript

  • Wątek modułów natywnych (Native Modules Thread). Tutaj wykonywane są wywołania modułów natywnych (np. UIManager). Nazwa wątku to mqt_native_modules lub <...>. Aby go zidentyfikować, szukaj elementów jak NativeCall, callJavaModuleMethod i onBatchComplete:

    Przykład wątku modułów natywnych

  • Bonus: Wątek renderowania (Render Thread). W Androidzie L (5.0+) występuje dodatkowy wątek generujący komendy OpenGL do rysowania UI. Nazwa wątku to RenderThread lub <...>. Identyfikuj go po elementach jak DrawFrame i queueBuffer:

    Przykład wątku renderowania

Identyfikowanie problemu

Płynna animacja powinna wyglądać tak:

Płynna animacja

Każda zmiana koloru to klatka – pamiętaj, że cała praca nad UI musi zmieścić się w 16 ms. Żaden wątek nie pracuje blisko granicy klatki. Tak renderowana aplikacja osiąga 60 FPS.

Jeśli występuje zacinanie, możesz zobaczyć:

Zacinanie spowodowane przez JS

Wątek JS działa niemal bez przerwy, przekraczając granice klatek! Aplikacja nie renderuje 60 FPS. Problem leży po stronie JavaScript.

Możesz też zobaczyć:

Zacinanie spowodowane przez UI

Wątki UI i renderowania pracują przez granice klatek. Renderowany interfejs wymaga zbyt wiele pracy. Problem leży w natywnych widokach.

Masz teraz kluczowe informacje do dalszych działań.

Rozwiązywanie problemów z JavaScript

Jeśli problem jest w JS, szukaj wskazówek w wykonywanym kodzie. W powyższym przykładzie RCTEventEmitter jest wywoływany wielokrotnie na klatkę. Powiększenie wątku JS:

Zbyt dużo operacji JS

To wygląda podejrzanie. Dlaczego tyle wywołań? Czy to różne zdarzenia? Odpowiedzi zależą od twojego kodu. Często warto sprawdzić shouldComponentUpdate.

Rozwiązywanie problemów z natywnym UI

Przy problemach z natywnym UI występują zwykle dwa scenariusze:

  1. renderowanie każdej klatki wymaga zbyt wiele pracy od GPU, lub

  2. Tworzysz nowy interfejs użytkownika w trakcie animacji/interakcji (np. wczytujesz nowe treści podczas przewijania).

Zbyt duże obciążenie GPU

W pierwszym scenariuszu zobaczysz ślad, w którym wątek interfejsu użytkownika i/lub wątek renderowania wyglądają tak:

Przeciążone GPU

Zwróć uwagę na długi czas spędzony w DrawFrame, który przekracza granice klatek. To czas oczekiwania, aż GPU opróżni swój bufor poleceń z poprzedniej klatki.

Aby rozwiązać ten problem:

  • rozważ użycie renderToHardwareTextureAndroid dla złożonych, statycznych treści poddawanych animacji/przekształceniom (np. animacje przesuwania/alfa w Navigator)

  • upewnij się, że nie używasz needsOffscreenAlphaCompositing (domyślnie wyłączone), ponieważ w większości przypadków znacząco zwiększa obciążenie GPU na klatkę.

Tworzenie nowych widoków w wątku UI

W drugim scenariuszu zobaczysz coś bardziej takiego:

Tworzenie widoków

Zauważ, że najpierw wątek JS wykonuje obliczenia, potem widoczna jest praca w wątku modułów natywnych, a następnie kosztowne przejście przez drzewo widoków w wątku UI.

Nie ma szybkiego rozwiązania tego problemu, chyba że możesz opóźnić tworzenie nowego UI do zakończenia interakcji lub uprościć tworzone elementy. Zespół React Native pracuje nad rozwiązaniem infrastrukturalnym, które umożliwi tworzenie i konfigurację nowego UI poza głównym wątkiem, zapewniając płynność interakcji.

Wyszukiwanie obciążonych fragmentów kodu natywnego

Jeśli problem wydaje się leżeć po stronie natywnej, użyj profilera obciążenia CPU. Otwórz panel Profilera w Android Studio i wybierz "Find CPU Hotspots (Java/Kotlin Method Recording)".

Wybierz nagrywanie Java/Kotlin

Upewnij się, że wybierasz "Find CPU Hotspots (Java/Kotlin Recording)" zamiast "Find CPU Hotspots (Callstack Sample)". Mają podobne ikony, ale różne funkcje.

Wykonaj interakcje i naciśnij "Stop recording". Nagrywanie jest zasobożerne, więc ogranicz interakcję do minimum. Wynik możesz przeanalizować w Android Studio lub wyeksportować i otworzyć w narzędziu online jak Firefox Profiler.

W przeciwieństwie do System Trace, profilowanie obciążenia CPU jest wolniejsze i nie da dokładnych pomiarów. Powinno jednak pokazać, które metody natywne są wywoływane i jak proporcjonalnie rozkłada się czas w każdej klatce.