Wiadomości o usługach

Konfigurowanie reguł Keep R8 i rozwiązywanie związanych z nimi problemów

Czas czytania: 7 minut
Ajesh Pai i Ben Weiss

W nowoczesnym programowaniu na Androida udostępnianie małej, szybkiej i bezpiecznej aplikacji jest podstawowym oczekiwaniem użytkowników. Głównym narzędziem systemu kompilacji Androida do tego celu jest optymalizator R8 , czyli kompilator, który usuwa nieużywany kod i zasoby w celu zmniejszenia rozmiaru aplikacji, zmiany nazw kodu lub minifikacji oraz optymalizacji aplikacji.

Włączenie R8 to kluczowy krok w przygotowaniu aplikacji do publikacji, ale wymaga od deweloperów podania wskazówek w postaci „zasad zachowania”.

Po przeczytaniu tego artykułu obejrzyj film Performance Spotlight Week w YouTube, w którym omawiamy włączanie, debugowanie i rozwiązywanie problemów z optymalizatorem R8.

 

 

Dlaczego potrzebne są reguły przechowywania

Konieczność pisania reguł Keep wynika z podstawowego konfliktu: R8 to narzędzie do analizy statycznej, ale aplikacje na Androida często opierają się na dynamicznych wzorcach wykonywania, takich jak odbicie lub wywołania w kodzie natywnym i z niego za pomocą JNI (Java Native Interface).

R8 tworzy wykres używanego kodu, analizując bezpośrednie wywołania. Gdy kod jest używany w dynamiczny sposób, analiza statyczna R8 nie może tego przewidzieć i oznacza go jako nieużywany, a następnie usuwa, co prowadzi do awarii w czasie działania.

Reguła zachowania to wyraźna instrukcja dla kompilatora R8, która mówi: „Ta konkretna klasa, metoda lub pole to punkt wejścia, do którego będzie się uzyskiwać dostęp dynamicznie w czasie działania. Musisz ją zachować, nawet jeśli nie możesz znaleźć do niej bezpośredniego odwołania”.

Więcej informacji o regułach przechowywania znajdziesz w oficjalnym przewodniku.

Gdzie pisać reguły Keep

Niestandardowe reguły zachowywania dla aplikacji są zapisywane w pliku tekstowym. Zgodnie z konwencją ten plik ma nazwę proguard-rules.pro i znajduje się w katalogu głównym modułu aplikacji lub biblioteki. Ten plik jest następnie określany w build.gradle.kts pliku modułu w release rodzaju kompilacji.

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

Używaj prawidłowego pliku domyślnego

Metoda getDefaultProguardFile importuje domyślny zestaw reguł dostarczonych przez pakiet Android SDK. Jeśli użyjesz nieprawidłowego pliku, aplikacja może nie być zoptymalizowana. Pamiętaj, aby używać proguard-android-optimize.txt. Ten plik zawiera domyślne reguły zachowywania dla standardowych komponentów Androida i umożliwia optymalizację kodu przez R8. Przestarzałe narzędzie proguard-android.txt udostępnia tylko reguły zachowywania, ale nie umożliwia optymalizacji R8.

progaurd.png

Jest to poważny problem z wydajnością, dlatego zaczynamy ostrzegać deweloperów przed używaniem nieprawidłowego pliku. Pierwsze ostrzeżenia pojawią się w Android Studio Narwhal 3 w ramach pakietu nowych funkcji. Od wersji 9.0 wtyczki Androida do obsługi Gradle nie obsługujemy już przestarzałego pliku proguard-android.txt. Pamiętaj więc, aby przejść na zoptymalizowaną wersję.

Jak pisać reguły Keep

Reguła zachowywania składa się z 3 głównych części:

  1. Opcja , np. -keep lub -keepclassmembers
  2. Opcjonalne modyfikatory, np. allowshrinking
  3. Specyfikacja klasy, która określa kod do dopasowania.

Pełną składnię i przykłady znajdziesz w instrukcji dodawania reguł przechowywania.

Antywzorce reguł Keep

Warto znać sprawdzone metody, ale też antyprzykłady. Te antypatenty często wynikają z nieporozumień lub skrótów w rozwiązywaniu problemów i mogą mieć katastrofalny wpływ na wydajność wersji produkcyjnej.

Opcje globalne

Te flagi to globalne przełączniki, których nigdy nie należy używać w kompilacji do publikacji. Służą one tylko do tymczasowego debugowania w celu wyodrębnienia problemu.

Użycie -dontotptimize skutecznie wyłącza optymalizacje wydajności R8, co powoduje spowolnienie działania aplikacji.

Użycie -dontobfuscate wyłącza wszystkie zmiany nazw, a użycie -dontshrink wyłącza usuwanie martwego kodu. Obie te reguły globalne zwiększają rozmiar aplikacji.

Aby zapewnić użytkownikom aplikacji większą wydajność, w miarę możliwości unikaj używania tych globalnych flag w środowisku produkcyjnym.

Zbyt ogólne reguły przechowywania

Najłatwiejszym sposobem na zniwelowanie korzyści z R8 jest napisanie zbyt ogólnych reguł przechowywania. Reguły takie jak ta poniżej instruują optymalizator R8, aby nie zmniejszał, nie zaciemniał ani nie optymalizował żadnej klasy w tym pakiecie ani żadnego z jego podpakietów. Całkowicie usuwa to korzyści z R8 w przypadku całego pakietu. Spróbuj utworzyć wąskie i szczegółowe reguły przechowywania.

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

Operator negacji (!)

Operator negacji (!) wydaje się skutecznym sposobem na wykluczenie pakietu z reguły. Ale to nie takie proste. Weźmy ten przykład:

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

Możesz pomyśleć, że ta reguła oznacza „nie zachowuj klas wcom.example.package”. W rzeczywistości oznacza ona jednak „zachowaj każdą klasę, metodę i właściwość w całej aplikacji, która nie znajduje się w com.example.package”. Jeśli to Cię zaskoczyło, sprawdź, czy w konfiguracji R8 nie ma żadnych negacji.

Nadmiarowe reguły dotyczące komponentów Androida

Kolejnym częstym błędem jest ręczne dodawanie reguł Keep dla ActivitiesServices lub BroadcastReceivers aplikacji. Jest to niepotrzebne. Domyślny plik proguard-android-optimize.txt zawiera już odpowiednie reguły, które umożliwiają działanie tych standardowych komponentów Androida od razu po zainstalowaniu.

Wiele bibliotek ma własne reguły przechowywania, więc nie musisz tworzyć własnych reguł. Jeśli wystąpi problem z regułami przechowywania w bibliotece, z której korzystasz, najlepiej skontaktować się z jej autorem, aby dowiedzieć się, na czym polega problem.

Sprawdzone metody dotyczące reguł Keep

Wiesz już, czego nie robić, więc teraz porozmawiajmy o sprawdzonych metodach.

Tworzenie wąskich reguł przechowywania

Reguły zachowywania powinny być jak najbardziej wąskie i szczegółowe. Powinny zachowywać tylko to, co jest niezbędne, aby umożliwić optymalizację wszystkiego innego przez R8.

RegułaJakość

 

-keep class com.example.** { ; }

 

Niski:zachowuje cały pakiet i jego podpakiet.

 

-keep class com.example.MyClass { ; }

 

Niski: zachowuje całą klasę, która prawdopodobnie jest nadal zbyt szeroka.
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
Wysoki: zachowywane są tylko odpowiednie metody i właściwości z określonej klasy.

Korzystanie ze wspólnych przodków

Zamiast pisać osobne reguły Keep dla wielu różnych modeli danych, napisz jedną regułę, która jest kierowana na wspólną klasę bazową lub interfejs. Poniższa reguła nakazuje R8 zachowanie wszystkich elementów klas, które implementują ten interfejs, i jest wysoce skalowalna.

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

Używanie adnotacji do kierowania na wiele zajęć

Utwórz niestandardową adnotację (np. @Serialize) i użyj jej do „tagowania” klas, których pola mają zostać zachowane. Jest to kolejny przejrzysty, deklaratywny i wysoce skalowalny wzorzec. Możesz też tworzyć reguły Keep dla istniejących już adnotacji z używanych przez Ciebie platform.

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

Wybieranie odpowiedniej opcji Keep

Opcja zachowania jest najważniejszą częścią reguły. Wybór nieprawidłowego ustawienia może niepotrzebnie wyłączyć optymalizację.

Opcja zachowaniaJak działa
-keepZapobiega usunięciu lub zmianie nazwy klasy i elementów wymienionych w deklaracji .
-keepclassmembersUniemożliwia usunięcie lub zmianę nazwy określonych członków, ale umożliwia usunięcie samych zajęć, o ile nie zostały one usunięte w inny sposób.
-keepclasseswithmembersKombinacja: zachowuje klasę i jej elementy tylko wtedy, gdy wszystkie określone elementy są obecne.

Więcej informacji o opcji zachowywania znajdziesz w naszej dokumentacji dotyczącej opcji zachowywania.

Zezwalaj na optymalizację za pomocą modyfikatorów

Modyfikatory takie jak allowshrinkingallowobfuscation łagodzą działanie ogólnej reguły -keep, przywracając R8 możliwość optymalizacji. Jeśli np. starsza biblioteka wymusza użycie adnotacji -keep w całej klasie, możesz odzyskać część optymalizacji, zezwalając na zmniejszanie i zaciemnianie:

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

Dodawanie opcji globalnych na potrzeby dodatkowej optymalizacji

Oprócz reguł zachowywania możesz dodać do pliku konfiguracyjnego R8 flagi globalne, aby jeszcze bardziej zwiększyć optymalizację.

-repackageclasses to zaawansowana opcja, która nakazuje R8 przeniesienie wszystkich zaciemnionych klas do jednego pakietu. Pozwala to zaoszczędzić dużo miejsca w pliku DEX dzięki usunięciu zbędnych ciągów znaków z nazwami pakietów.

-allowaccessmodification umożliwia R8 rozszerzenie dostępu (np. private na public), aby umożliwić bardziej agresywne wstawianie. Jest to teraz domyślnie włączone podczas korzystania z proguard-android-optimize.txt.

Ostrzeżenie: autorzy bibliotek nigdy nie mogą dodawać tych globalnych flag optymalizacji do reguł konsumenta, ponieważ byłyby one wymuszane w całej aplikacji.

Aby jeszcze bardziej to ułatwić, w wersji 9.0 wtyczki Androida do obsługi Gradle zaczniemy całkowicie ignorować globalne flagi optymalizacji z bibliotek. 

Sprawdzone metody dotyczące bibliotek

Każda aplikacja na Androida w taki czy inny sposób korzysta z bibliotek. Porozmawiajmy więc o sprawdzonych metodach dotyczących bibliotek.

Dla deweloperów bibliotek

Jeśli Twoja biblioteka korzysta z odbicia lub JNI, musisz udostępnić niezbędne reguły Keep jej użytkownikom. Te reguły są umieszczane w pliku consumer-rules.pro, który jest następnie automatycznie dołączany do pliku AAR biblioteki.

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

Dla użytkowników biblioteki

Odfiltrowywanie problematycznych reguł Keep

Jeśli musisz użyć biblioteki, która zawiera problematyczne reguły Keep, możesz je odfiltrować w pliku build.gradle.kts, zaczynając od AGP 9.0. Dzięki temu R8 będzie ignorować reguły pochodzące z określonej zależności.

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

Najlepsza reguła przechowywania to brak reguły przechowywania

Najlepsza strategia konfiguracji R8 polega na całkowitym wyeliminowaniu potrzeby pisania reguł Keep. W przypadku wielu aplikacji można to osiągnąć, wybierając nowoczesne biblioteki, które preferują generowanie kodu zamiast odbicia. Dzięki generowaniu kodu optymalizator może łatwiej określić, który kod jest faktycznie używany w czasie działania, a który można usunąć. Nieużywanie odbicia dynamicznego oznacza również brak „ukrytych” punktów wejścia, a co za tym idzie – brak potrzeby stosowania reguł Keep. Wybierając nową bibliotekę, zawsze preferuj rozwiązanie, które używa generowania kodu zamiast odbicia.

Więcej informacji o wybieraniu bibliotek znajdziesz w artykule Rozważne wybieranie bibliotek.

Debugowanie i rozwiązywanie problemów z konfiguracją R8

Jeśli R8 usunie kod, który powinien zachować, lub Twój plik APK jest większy niż oczekiwano, użyj tych narzędzi, aby zdiagnozować problem.

Znajdowanie zduplikowanych i globalnych reguł przechowywania

R8 łączy reguły z dziesiątek źródeł, więc trudno określić „ostateczny” zestaw reguł. Dodanie tego flagi do pliku proguard-rules.pro generuje pełny raport:

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

Możesz przeszukać ten plik, aby znaleźć zbędne reguły lub prześledzić problematyczną regułę (np. -dontoptimize) i odnaleźć konkretną bibliotekę, która ją zawiera.

Zapytaj R8: Dlaczego to zachowujesz?

Jeśli zajęcia, które miały zostać usunięte, nadal są widoczne w aplikacji, R8 może podać tego przyczynę. Wystarczy dodać tę regułę:

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

Podczas kompilacji R8 wyświetli dokładny łańcuch odwołań, które spowodowały zachowanie tej klasy, co pozwoli Ci prześledzić odwołanie i dostosować reguły.

Pełny przewodnik znajdziesz w sekcji Rozwiązywanie problemów z R8.

Dalsze kroki

R8 to zaawansowane narzędzie do zwiększania wydajności aplikacji na Androida. Jego skuteczność zależy od prawidłowego zrozumienia jego działania jako statycznego silnika analizy.

Tworząc szczegółowe reguły na poziomie elementów, wykorzystując elementy nadrzędne i adnotacje oraz starannie wybierając odpowiednie opcje zachowywania, możesz zachować dokładnie to, co jest potrzebne. Najbardziej zaawansowaną praktyką jest całkowite wyeliminowanie potrzeby stosowania reguł poprzez wybieranie nowoczesnych bibliotek opartych na generowaniu kodu zamiast ich poprzedników opartych na odbiciu.

W ramach Tygodnia Najlepszych Wyników obejrzyj dzisiejszy film na YouTube i kontynuuj wyzwanie R8. W przypadku pytań dotyczących włączania R8 lub rozwiązywania problemów z nim używaj tagu #optimizationEnabled. Chętnie Ci pomożemy.

Czas przekonać się o korzyściach.

Zachęcamy do włączenia w aplikacji trybu pełnego R8 już dziś.

  1. Aby rozpocząć, zapoznaj się z naszymi przewodnikami dla deweloperów: Włączanie optymalizacji aplikacji.
  2. Sprawdź, czy nadal używasz znaku proguard-android.txt, i zastąp go znakiem proguard-android-optimize.txt.
  3. Następnie zmierzyć wpływ. Nie tylko poczuj różnicę, ale też ją sprawdź. Aby zmierzyć wzrost wydajności, dostosuj kod z naszej  przykładowej aplikacji do testów porównawczych na GitHubie i zmierz czasy uruchamiania przed i po zmianach.

Jesteśmy przekonani, że zauważysz znaczną poprawę wyników aplikacji.

Przy okazji użyj tagu społecznościowego #AskAndroid, aby zadać pytania. Nasi eksperci przez cały tydzień monitorują i odpowiadają na Twoje pytania.

Jutro opowiemy o optymalizacji opartej na profilu z profilami podstawowymi i profilami uruchamiania, pokażemy, jak w ostatnich wersjach poprawiła się wydajność renderowania w Compose, i omówimy kwestie związane z wydajnością pracy w tle.

Autor:

Czytaj dalej