Wiadomości o usługach

Konfigurowanie reguł zachowywania 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 osiągnięcia tego celu jest optymalizator R8 , czyli kompilator, który obsługuje usuwanie nieużywanego kodu i zasobów w celu zmniejszenia rozmiaru aplikacji, zmiany nazw kodu lub minifikacji oraz optymalizacji aplikacji.

Włączenie R8 jest kluczowym krokiem 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 programu. Musisz go zachować, nawet jeśli nie możesz znaleźć bezpośredniego odniesienia do niego”.

Więcej informacji o zasadach 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 rodzaju kompilacji release pliku build.gradle.kts modułu.

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

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

        "proguard-rules.pro",

    )

}

Używanie 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 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 z pakietem nowych funkcji. Od wersji 9.0 wtyczki Androida do obsługi Gradle nie obsługujemy już przestarzałego pliku proguard-android.txt. Dlatego pamiętaj, aby przejść na zoptymalizowaną wersję.

Jak pisać reguły Keep

Reguła przechowywania 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 antypatie często wynikają z nieporozumień lub skrótów w rozwiązywaniu problemów i mogą mieć katastrofalny wpływ na wydajność kompilacji 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ść, unikaj używania tych globalnych flag w środowisku produkcyjnym, gdy tylko jest to możliwe.

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. Spowoduje to całkowite usunięcie korzyści z R8 w przypadku całego pakietu. Spróbuj utworzyć wąskie i konkretne reguły Keep.

-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 Activities, Services lub BroadcastReceivers w 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 wyjęciu z pudełka.

Wiele bibliotek ma też własne reguły przechowywania. Nie musisz więc pisać własnych reguł. Jeśli wystąpi problem z zasadami Keep w używanej bibliotece, 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

Dobre reguły przechowywania powinny być jak najbardziej szczegółowe i precyzyjne. Powinny zachowywać tylko to, co jest niezbędne, aby R8 mógł zoptymalizować wszystko inne.

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ć oddzielne reguły Keep dla wielu różnych modeli danych, napisz jedną regułę, która będzie dotyczyć wspólnej klasy bazowej lub interfejsu. 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łowej opcji 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.
-keepclasseswithmembersPołączenie: zachowuje klasę i jej członków tylko wtedy, gdy są obecni wszyscy określeni członkowie.

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ą ogólną regułę -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. z private na public), aby umożliwić bardziej agresywne wstawianie kodu. Jest ona teraz domyślnie włączona podczas korzystania z proguard-android-optimize.txt.

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

Aby jeszcze bardziej to wyjaśnić, 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 mniejszym lub większym stopniu korzysta z bibliotek. Omówmy więc sprawdzone metody dotyczące 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, począwszy 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 konieczności 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 programu, a który można usunąć. Brak dynamicznego odbicia oznacza też brak „ukrytych” punktów wejścia, a co za tym idzie, nie są potrzebne żadne reguły Keep Rules. Wybierając nową bibliotekę, zawsze preferuj rozwiązanie, które wykorzystuje generowanie kodu zamiast odbicia.

Więcej informacji o wybieraniu bibliotek znajdziesz w artykule Wybieraj biblioteki z rozsądkiem.

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.

Ask R8: Why are you keeping this?

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 silnika analizy statycznej.

Tworząc szczegółowe reguły na poziomie członka, korzystając z przodków i adnotacji oraz starannie wybierając odpowiednie opcje przechowywania, możesz zachować dokładnie to, co jest potrzebne. Najbardziej zaawansowanym rozwiązaniem jest całkowite wyeliminowanie potrzeby stosowania reguł przez wybieranie nowoczesnych bibliotek opartych na generowaniu kodu zamiast ich poprzedników opartych na odbiciu.

W ramach Tygodnia poświęconego skuteczności 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 pełnego trybu R8 w swojej aplikacji już dziś.

  1. Aby rozpocząć, zapoznaj się z naszymi przewodnikami dla deweloperów: Włącz optymalizację aplikacji.
  2. Sprawdź, czy nadal używasz proguard-android.txt, i zastąp go symbolem proguard-android-optimize.txt.
  3. Następnie zbadaj 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 wprowadzeniu zmian.

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 z użyciem profili, w tym profili podstawowych i startowych, 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