Konfigurowanie reguł zachowywania R8 i rozwiązywanie związanych z nimi problemów
7 min czytania
W nowoczesnym tworzeniu aplikacji na Androida podstawowym oczekiwaniem użytkowników jest, aby aplikacja była mała, szybka i bezpieczna. Głównym narzędziem systemu kompilacji Androida do osiągnięcia tego celu jest optymalizator R8 – kompilator, który usuwa martwy kod i zasoby w celu zmniejszenia rozmiaru aplikacji, zmiany nazw kodu lub jego 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 „reguł zachowywania”.
Po przeczytaniu tego artykułu obejrzyj na YouTube film z serii Performance Spotlight Week o włączaniu, debugowaniu i rozwiązywaniu problemów z optymalizatorem R8.
Dlaczego potrzebne są reguły zachowywania
Konieczność pisania reguł zachowywania wynika z podstawowego konfliktu: R8 to narzędzie do analizy statycznej, ale aplikacje na Androida często korzystają z dynamicznych wzorców wykonywania, takich jak odbicie lub wywołania do i z kodu natywnego za pomocą JNI (Java Native Interface).
R8 tworzy wykres używanego kodu, analizując bezpośrednie wywołania. Gdy kod jest dostępny w sposób dynamiczny, analiza statyczna R8 nie może tego przewidzieć i zidentyfikuje ten kod jako nieużywany i go usunie, co spowoduje awarie w czasie działania.
Reguła zachowywania to wyraźna instrukcja dla kompilatora R8: „Ta konkretna klasa, metoda lub pole jest punktem 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 odniesienia”.
Więcej informacji o regułach zachowywania znajdziesz w oficjalnym przewodniku.
Gdzie pisać reguły zachowywania
Niestandardowe reguły zachowywania 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 w pliku build.gradle.kts modułu.
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 Android SDK. Jeśli używasz nieprawidłowego pliku, aplikacja może nie być zoptymalizowana. Upewnij się, że używasz pliku proguard-android-optimize.txt. Ten plik zawiera domyślne reguły zachowywania standardowych komponentów Androida oraz włącza optymalizacje kodu R8. Przestarzały plik proguard-android.txt zawiera tylko reguły zachowywania, ale nie włącza optymalizacji R8.
Ponieważ jest to poważny problem z wydajnością, zaczynamy ostrzegać deweloperów przed używaniem nieprawidłowego pliku. Pierwsze ostrzeżenia pojawią się w Android Studio Narwhal 3 Feature Drop.Od wersji 9.0 wtyczki Androida do obsługi Gradle nie obsługujemy już przestarzałego proguard-android.txt pliku. Dlatego upewnij się, że przechodzisz na zoptymalizowaną wersję.
Jak pisać reguły zachowywania
Reguła zachowywania składa się z 3 głównych części:
- Opcja , np.
-keeplub-keepclassmembers - Opcjonalne modyfikatory , np.
allowshrinking - Specyfikacja klasy , która definiuje kod do dopasowania
Pełną składnię i przykłady znajdziesz w przewodniku dodawania reguł zachowywania.
Antywzorce reguł zachowywania
Warto znać sprawdzone metody, ale też antywzorce. Te antywzorce często wynikają z nieporozumień lub skrótów w rozwiązywaniu problemów i mogą mieć katastrofalne skutki dla wydajności kompilacji produkcyjnej.
Opcje globalne
Te flagi to przełączniki globalne, których nigdy nie należy używać w kompilacji do publikacji. Służą one tylko do tymczasowego debugowania w celu wyizolowania problemu.
Użycie -dontotptimize skutecznie wyłącza optymalizacje wydajności R8, co powoduje spowolnienie 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 lepszą wydajność, unikaj używania tych flag globalnych w środowisku produkcyjnym.
Zbyt ogólne reguły zachowywania
Najłatwiejszym sposobem na zniwelowanie korzyści z R8 jest napisanie zbyt ogólnych reguł zachowywania. Reguły zachowywania, takie jak ta poniżej, instruują optymalizator R8, aby nie zmniejszał rozmiaru, nie zaciemniał i nie optymalizował żadnej klasy w tym pakiecie ani żadnego z jego podpakietów. Całkowicie usuwa to korzyści z R8 dla całego pakietu. Zamiast tego spróbuj pisać wąskie i konkretne reguły zachowywania.
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMSOperator negacji (!)
Operator negacji (!) wydaje się skutecznym sposobem na wykluczenie pakietu z reguły. Ale to nie takie proste. Spójrz na ten przykład:
-keep class !com.example.my_package.** { *; } // USE WITH CAUTIONMożesz pomyśleć, że ta reguła oznacza „nie zachowuj klas wcom.example.package.”. W rzeczywistości oznacza ona „zachowaj każdą klasę, metodę i właściwość w całej aplikacji, która nie znajduje się w com.example.package.”. Jeśli Cię to 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ł zachowywania dla Activities, Services 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.
Wiele bibliotek ma też własne reguły zachowywania. Dlatego nie musisz pisać własnych reguł. Jeśli wystąpi problem z regułami zachowywania z używanej biblioteki, najlepiej skontaktować się z autorem biblioteki, aby dowiedzieć się, na czym polega problem.
Sprawdzone metody dotyczące reguł zachowywania
Wiesz już, czego nie robić, więc porozmawiajmy o sprawdzonych metodach.
Pisz wąskie reguły zachowywania
Dobre reguły zachowywania powinny być jak najwęższe i jak najbardziej szczegółowe. Powinny zachowywać tylko to, co jest niezbędne, aby R8 mógł zoptymalizować wszystko inne.
| Reguła | Jakość |
|---|---|
| Niska: zachowuje cały pakiet i jego podpakiety |
| Niska: 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);
} | Wysoka: zachowuje tylko odpowiednie metody i właściwości z określonej klasy |
Używaj wspólnych przodków
Zamiast pisać oddzielne reguły zachowywania dla wielu różnych modeli danych, napisz jedną regułę, która będzie 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żywaj adnotacji, aby kierować na wiele klas
Utwórz adnotację niestandardową (np. @Serialize) i użyj jej do „oznaczenia” klas, których pola mają być zachowane. Jest to kolejny czysty, deklaratywny i wysoce skalowalny wzorzec. Możesz też tworzyć reguły zachowywania dla już istniejących adnotacji z używanych przez Ciebie frameworków.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}Wybierz odpowiednią opcję zachowywania
Opcja zachowywania jest najważniejszą częścią reguły. Wybranie nieprawidłowej opcji może niepotrzebnie wyłączyć optymalizację.
| Opcja zachowywania | Co robi |
-keep | Zapobiega usunięciu lub zmianie nazwy klasy i elementów wymienionych w deklaracji . |
-keepclassmembers | Zapobiega usunięciu lub zmianie nazwy określonych elementów, ale umożliwia usunięcie samej klasy, ale tylko w przypadku klas, które nie są usuwane w inny sposób. |
-keepclasseswithmembers | Połączenie: zachowuje klasę i jej elementy, tylko wtedy, gdy wszystkie określone elementy są obecne. |
Więcej informacji o opcji zachowywania znajdziesz w naszej dokumentacji opcji zachowywania.
Zezwalaj na optymalizację za pomocą modyfikatorów
Modyfikatory takie jak allowshrinking i allowobfuscation rozluźniają ogólną regułę -keep, przywracając R8 możliwość optymalizacji. Jeśli np. starsza biblioteka wymusza użycie -keep w całej klasie, możesz odzyskać część optymalizacji, zezwalając na zmniejszanie rozmiaru 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
Dodaj opcje globalne, aby uzyskać dodatkową optymalizację
Oprócz reguł zachowywania możesz dodać do pliku konfiguracyjnego R8 flagi globalne, aby zachęcić do jeszcze większej optymalizacji.
-repackageclasses to zaawansowana opcja, która instruuje R8, aby przeniósł wszystkie zaciemnione klasy do jednego pakietu. Pozwala to zaoszczędzić sporo miejsca w pliku DEX, usuwając zbędne ciągi nazw pakietów.
-allowaccessmodification umożliwia R8 rozszerzenie dostępu (np. z private na public), aby umożliwić bardziej agresywne wstawianie. Jest to teraz domyślnie włączone podczas używania proguard-android-optimize.txt.
Ostrzeżenie: autorzy bibliotek nigdy nie mogą dodawać tych globalnych flag optymalizacji do reguł konsumenta, ponieważ będą one wymuszane w całej aplikacji.
Aby było to jeszcze bardziej jasne, 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 używa odbicia lub JNI, musisz udostępnić jej użytkownikom niezbędne reguły zachowywania. Reguły te 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 bibliotek
Filtruj problematyczne reguły zachowywania
Jeśli musisz używać biblioteki, która zawiera problematyczne reguły zachowywania, możesz je odfiltrować w pliku build.gradle.kts od wersji 9.0 AGP. Informuje to R8, aby 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 zachowywania to brak reguły zachowywania
Najlepszą strategią konfiguracji R8 jest całkowite wyeliminowanie potrzeby pisania reguł zachowywania. 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 rzeczywiście używany w czasie działania, a który można usunąć. Ponadto brak użycia odbicia dynamicznego oznacza brak „ukrytych” punktów wejścia, a co za tym idzie, nie są potrzebne żadne reguły zachowywania. Podczas wybierania nowej biblioteki zawsze preferuj rozwiązanie, które używa generowania kodu zamiast odbicia.
Więcej informacji o tym, jak wybrać biblioteki, znajdziesz w artykule Wybieranie biblioteki.
Debugowanie i rozwiązywanie problemów z konfiguracją R8
Jeśli R8 usunie kod, który powinien zachować, lub jeśli plik APK jest większy niż oczekiwano, użyj tych narzędzi, aby zdiagnozować problem.
Znajdź zduplikowane i globalne reguły zachowywania
Ponieważ R8 scala reguły z kilkudziesięciu źródeł, trudno jest określić „ostateczny” zestaw reguł. Dodanie tej 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źć nadmiarowe reguły lub prześledzić problematyczną regułę (np. -dontoptimize) do konkretnej biblioteki, która ją zawiera.
Zapytaj R8: dlaczego to zachowujesz?
Jeśli klasa, która miała zostać usunięta, nadal znajduje się w aplikacji, R8 może Ci powiedzieć, dlaczego. Po prostu dodaj 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 odniesień, które spowodowały zachowanie tej klasy, co pozwoli Ci prześledzić odniesienie 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.
Pisząc konkretne reguły na poziomie elementów, wykorzystując przodków i adnotacje oraz starannie wybierając odpowiednie opcje zachowywania, możesz zachować dokładnie to, co jest niezbędne. Najbardziej zaawansowaną metodą jest całkowite wyeliminowanie potrzeby stosowania reguł przez wybranie nowoczesnych bibliotek opartych na generowaniu kodu zamiast ich poprzedników opartych na odbiciu.
W ramach Performance Spotlight Week obejrzyj dzisiejszy film z tej serii na YouTube i kontynuuj wyzwanie R8. Jeśli masz pytania dotyczące włączania R8 lub rozwiązywania problemów z nim, użyj tagu #optimizationEnabled. Chętnie Ci pomożemy.
Czas przekonać się o korzyściach.
Zachęcamy Cię, abyś dziś włączył w swojej aplikacji pełny tryb R8.
- Na początek zapoznaj się z naszymi przewodnikami dla deweloperów: Włączanie optymalizacji aplikacji.
- Sprawdź, czy nadal używasz pliku
proguard-android.txt, i zastąp go plikiemproguard-android-optimize.txt. - 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 Macrobenchmark na GitHubie , aby zmierzyć czasy uruchamiania przed i po.
Jesteśmy przekonani, że zauważysz znaczną poprawę wydajności aplikacji.
Jeśli masz pytania, użyj tagu #AskAndroid. Nasi eksperci będą przez cały tydzień monitorować Twoje pytania i na nie odpowiadać.
Wróć jutro, aby dowiedzieć się więcej o optymalizacji opartej na profilu z profilami bazowymi i profilami uruchamiania, o tym, jak w ostatnich wersjach poprawiła się wydajność renderowania w Compose, oraz o kwestiach związanych z wydajnością pracy w tle.
-
Nowości dotyczące produktówTo ostatnia stabilna wersja Android Studio Quail. Nowe funkcje w Android Studio umożliwiają wydajne i skuteczne tworzenie aplikacji premium z wykorzystaniem AI.
Amman Asfaw • 5 min czytania -
Nowości dotyczące produktówUtrzymanie zdrowego ekosystemu Androida to wspólne zobowiązanie, w którym każda aplikacja i gra ma do odegrania swoją rolę.
Raghavendra Hareesh Pottamsetty • 4 min czytania -
Nowości dotyczące produktówW Google Play bezpieczeństwo użytkowników i sukces deweloperów idą w parze. Stale obserwujemy wzrost liczby aplikacji z funkcjami generowanymi przez AI. Dodanie generatywnej AI do aplikacji to świetny sposób na odblokowanie niesamowitych możliwości twórczych.
Ron Aquino • 4 min czytania
Otrzymuj co tydzień najnowsze informacje o tworzeniu aplikacji na Androida na swoją skrzynkę odbiorczą.