Jako autor biblioteki musisz zadbać o to, aby deweloperzy aplikacji mogli łatwo włączyć Twoją bibliotekę do swoich aplikacji, zachowując przy tym wysoką jakość obsługi użytkowników. Oznacza to, że Twoja biblioteka musi być zgodna z optymalizacją Androida (R8) bez konieczności dodatkowej konfiguracji przez dewelopera. Możesz też udokumentować, że biblioteka może być nieodpowiednia do użycia na Androidzie. Kluczowe jest, aby biblioteki przeznaczone do użycia na Androidzie nie uniemożliwiały ważnych optymalizacji aplikacji i były zgodne z dodatkowymi wymaganiami dotyczącymi optymalizacji.
Ta dokumentacja jest przeznaczona dla deweloperów opublikowanych bibliotek, ale może być też przydatna dla deweloperów wewnętrznych modułów bibliotek w dużej, modułowej aplikacji.
Jeśli jesteś deweloperem aplikacji i chcesz dowiedzieć się, jak zoptymalizować aplikację na Androida, przeczytaj artykuł Włączanie optymalizacji aplikacji. Aby dowiedzieć się, które biblioteki są odpowiednie do użycia, przeczytaj artykuł Wybieranie bibliotek.
Typy reguł zachowywania
W bibliotekach możesz mieć 2 różne typy reguł zachowywania:
- Reguły zachowywania konsumenta muszą określać reguły, które zachowują wszystko, co odzwierciedla biblioteka. Jeśli biblioteka używa odbicia lub JNI do wywoływania swojego kodu lub kodu zdefiniowanego przez aplikację klienta, te reguły muszą opisywać, który kod należy zachować. Biblioteki powinny zawierać reguły zachowywania konsumenta, które mają taki sam format jak reguły zachowywania aplikacji. Te reguły są dołączane do artefaktów biblioteki (AAR lub JAR) i są automatycznie używane podczas optymalizacji aplikacji na Androida, gdy biblioteka jest używana. Te reguły są przechowywane w
pliku określonym przez właściwość
consumerProguardFileswbuild.gradle.kts(lubbuild.gradle) pliku. Więcej informacji znajdziesz w artykule Pisanie reguł zachowywania konsumenta. - Reguły zachowywania kompilacji biblioteki są stosowane podczas kompilowania biblioteki. Są one potrzebne tylko wtedy, gdy zdecydujesz się częściowo zoptymalizować bibliotekę w czasie kompilacji. Muszą one uniemożliwiać usunięcie publicznego interfejsu API biblioteki, w przeciwnym razie nie będzie on dostępny w dystrybucji biblioteki, co oznacza, że deweloperzy aplikacji nie będą mogli używać biblioteki. Te reguły są przechowywane w pliku
określonym przez właściwość
proguardFilesw plikubuild.gradle.kts(lubbuild.gradle). Więcej informacji znajdziesz w artykule Optymalizowanie kompilacji biblioteki AAR.
Wymagania i wytyczne dotyczące optymalizacji
Konfiguracja R8 w bibliotekach ma globalny wpływ na ostateczny rozmiar binarny i wydajność aplikacji. Oprócz ogólnych sprawdzonych metod dotyczących reguł zachowywania autorzy bibliotek muszą przestrzegać określonych wymagań i stosować się do dodatkowych wytycznych.
Przestrzeganie wymagań dotyczących optymalizacji
Niewydajność bibliotek jest główną przyczyną rozrostu aplikacji, marnowania pamięci, powolnego uruchamiania i błędów Aplikacja nie odpowiada (ANR). Aby uniknąć znacznego obniżenia jakości aplikacji i wygody użytkowników, biblioteki muszą unikać naruszania tych wymagań.
Brak ogólnych reguł zachowywania lub reguł zachowywania dla całego pakietu: Twoja biblioteka nie może zawierać ogólnych reguł zachowywania, które zachowują większość kodu w bibliotece lub w innej bibliotece. Ogólne reguły zachowywania mogą rozwiązać problemy z awariami w krótkim okresie, ale zwiększają rozmiar aplikacji we wszystkich aplikacjach, które korzystają z Twojej biblioteki.
Nie umieszczaj reguł zachowywania dla całego pakietu (np.
-keep class com.mylibrary.** {*; }) w przypadku pakietów w bibliotece lub innych bibliotekach, do których się odwołujesz. Takie reguły ograniczają optymalizację tych pakietów we wszystkich aplikacjach, które korzystają z Twojej biblioteki.Brak nieodpowiednich reguł globalnych: nigdy nie używaj opcji globalnych, takich jak
-dontobfuscateczy-allowaccessmodification.W miarę możliwości używaj generowania kodu zamiast odbicia: jeśli to możliwe, używaj generowania kodu (codegen) zamiast odbicia. Generowanie kodu i odbicie to powszechne metody unikania powtarzalnego kodu podczas programowania, ale generowanie kodu jest bardziej zgodne z optymalizatorem aplikacji, takim jak R8.
W przypadku generowania kodu kod jest analizowany i modyfikowany podczas procesu kompilacji. Ponieważ po kompilacji nie ma żadnych większych modyfikacji, optymalizator wie, który kod jest ostatecznie potrzebny, a który można bezpiecznie usunąć.
W przypadku odbicia kod jest analizowany i manipulowany w czasie działania. Ponieważ kod nie jest ostatecznie gotowy do momentu wykonania, optymalizator nie wie, który kod można bezpiecznie usunąć. Prawdopodobnie usunie kod, który jest używany dynamicznie za pomocą odbicia w czasie działania, co powoduje awarie aplikacji u użytkowników.
Wiele nowoczesnych bibliotek używa generowania kodu zamiast odbicia. Więcej informacji znajdziesz w artykule KSP – wspólny punkt wejścia używany przez Room, Dagger2 i wiele innych.
Obsługa pełnego trybu R8: Twoja biblioteka nie powinna się zawieszać, gdy włączony jest pełny tryb R8. Pełny tryb R8 to zalecany tryb używania R8. Jest on domyślnie włączony od wersji AGP 8.0, która została ustabilizowana w 2023 r. Jeśli Twoja biblioteka zawiesza się w R8, rozwiązaniem jest zidentyfikowanie konkretnego punktu wejścia odbicia lub JNI i dodanie reguły docelowej, a nie zachowanie całego pakietu.
Dodatkowe zalecenia
Oprócz wymagań dotyczących optymalizacji obowiązują też te dodatkowe zalecenia.
- Nie używaj
-repackageclassesw pliku reguł zachowywania konsumenta biblioteki. Aby jednak zoptymalizować kompilację biblioteki, możesz użyć-repackageclassesz wewnętrzną nazwą pakietu, np.<your.library.package>.internal, w pliku reguł zachowywania kompilacji biblioteki. Może to zwiększyć wydajność biblioteki w niezoptymalizowanych aplikacjach. Nie jest to jednak zwykle konieczne, ponieważ aplikacje też powinny być zoptymalizowane. - Zadeklaruj wszystkie atrybuty potrzebne do działania biblioteki w plikach reguł zachowywania biblioteki, nawet jeśli mogą się one pokrywać z atrybutami zdefiniowanymi w
proguard-android-optimize.txt. - Jeśli w dystrybucji biblioteki potrzebujesz tych atrybutów, zachowaj je w pliku reguł zachowywania kompilacji biblioteki, a nie w pliku reguł zachowywania konsumenta biblioteki:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Jeśli adnotacje są używane w czasie działania, autorzy bibliotek powinni zachować atrybut
RuntimeVisibleAnnotationsw regułach zachowywania konsumenta. - Autorzy bibliotek nie powinni używać tych opcji globalnych w regułach zachowywania konsumenta:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Kiedy można używać odbicia
Jeśli musisz używać odbicia, powinieneś/powinnaś używać go tylko w przypadku tych elementów:
- Konkretne typy docelowe (konkretne implementacje interfejsów lub podklasy)
- Kod używający konkretnej adnotacji w czasie działania
Używanie odbicia w ten sposób ogranicza koszty w czasie działania i umożliwia pisanie docelowych reguł zachowywania konsumenta.
Ta konkretna i docelowa forma odbicia to wzorzec, który można zobaczyć w platformie Androida (np. podczas rozszerzania aktywności, widoków i obiektów rysowalnych) i w bibliotekach AndroidX (np. podczas tworzenia WorkManager
ListenableWorkers, lub RoomDatabases). Natomiast otwarte odbicie Gson nie nadaje się do użycia w aplikacjach na Androida.
Często występujące nieporozumienia
Kilka często występujących nieporozumień może prowadzić do nieprawidłowej konfiguracji R8. Oto niektóre z nich:
Nieprawidłowe zrozumienie optymalizacji R8: wbrew powszechnemu przekonaniu optymalizacje R8 nie ograniczają się tylko do zaciemniania kodu, ale obejmują też zmniejszanie kodu i optymalizacje logiczne za pomocą technik wstawiania metod i łączenia klas. Więcej informacji znajdziesz w artykule Omówienie optymalizacji R8.
Pomijanie optymalizacji zaciemnionych bibliotek: częstym błędem jest pomijanie optymalizacji biblioteki, ponieważ została ona zoptymalizowana lub zaciemniona podczas kompilowania do pliku AAR (Android Archive) lub JAR (Java Archive). Optymalizacje w czasie kompilacji biblioteki są ograniczone, a aplikacja nie powinna wyłączać optymalizacji biblioteki przez uwzględnienie jej w regule zachowywania. Więcej informacji znajdziesz w artykule Optymalizowanie kompilacji biblioteki AAR.
Nieprawidłowe zrozumienie opcji
-keepReguła-keepuniemożliwia R8 uruchamianie dowolnych etapów optymalizacji. Więcej informacji znajdziesz w artykule Wybieranie odpowiedniej opcji zachowywania.
Konfigurowanie pakowania reguł
Aby reguły zachowywania konsumenta były stosowane prawidłowo, musisz je odpowiednio spakować w zależności od formatu biblioteki.
Biblioteki AAR
Aby dodać reguły konsumenta do biblioteki AAR, użyj opcji consumerProguardFiles w skrypcie kompilacji modułu biblioteki Androida. Więcej informacji znajdziesz w naszych
wskazówkach dotyczących tworzenia modułów bibliotek.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Dynamiczny
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Biblioteki JAR
Aby dołączyć reguły do biblioteki Kotlin lub Java, która jest dostarczana jako plik JAR, umieść plik reguł w katalogu META-INF/proguard/ w końcowym pliku JAR z dowolną nazwą pliku.
Jeśli np. kod znajduje się w <libraryroot>/src/main/kotlin, umieść plik reguł konsumenta w
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
, a reguły zostaną dołączone w prawidłowej lokalizacji w wyjściowym pliku JAR.
Sprawdź, czy końcowy plik JAR prawidłowo dołącza reguły, sprawdzając, czy znajdują się one w katalogu META-INF/proguard.
Optymalizowanie kompilacji biblioteki AAR (zaawansowane)
Zwykle nie trzeba bezpośrednio optymalizować kompilacji biblioteki, ponieważ możliwe optymalizacje w czasie kompilacji biblioteki są bardzo ograniczone. Zanim zoptymalizujesz bibliotekę, jako deweloper musisz zastanowić się nad kilkoma etapami optymalizacji i zachowania, zarówno w czasie kompilacji biblioteki, jak i aplikacji.
Jeśli nadal chcesz zoptymalizować bibliotekę w czasie kompilacji, wtyczka Androida do obsługi Gradle to umożliwia.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Dynamiczny
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Pamiętaj, że działanie proguardFiles bardzo różni się od działania consumerProguardFiles:
proguardFilessą używane w czasie kompilacji, często razem zgetDefaultProguardFile("proguard-android-optimize.txt"), aby określić, która część biblioteki powinna zostać zachowana podczas kompilacji biblioteki. Co najmniej jest to publiczny interfejs API.consumerProguardFilessą natomiast pakowane do biblioteki, aby wpływać na optymalizacje, które będą wykonywane później, podczas kompilacji aplikacji korzystającej z Twojej biblioteki.
Jeśli np. biblioteka używa odbicia do tworzenia klas wewnętrznych, może być konieczne zdefiniowanie reguł zachowywania zarówno w proguardFiles, jak i consumerProguardFiles.
Jeśli używasz -repackageclasses w kompilacji biblioteki, zmień nazwę pakietu klas na podpakiet wewnątrz pakietu biblioteki. Użyj np. -repackageclasses
'com.example.mylibrary.internal' zamiast -repackageclasses 'internal'.
Obsługa różnych wersji R8 (zaawansowane)
Możesz dostosować reguły do konkretnych wersji R8. Dzięki temu Twoja biblioteka będzie optymalnie działać w projektach, które używają nowszych wersji R8, a dotychczasowe reguły będą nadal używane w projektach ze starszymi wersjami R8.
Aby określić docelowe reguły R8, musisz je uwzględnić w katalogu META-INF/com.android.tools wewnątrz classes.jar pliku AAR lub w katalogu META-INF/com.android.tools pliku JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
W katalogu META-INF/com.android.tools może znajdować się kilka podkatalogów o nazwach w formacie r8-from-<X>-upto-<Y> aby wskazać dla których wersji R8 zostały napisane reguły. Każdy podkatalog może zawierać co najmniej 1 plik z regułami R8 o dowolnych nazwach i rozszerzeniach.
Pamiętaj, że części -from-<X> i -upto-<Y> są opcjonalne, wersja <Y>
jest wyłączna, a zakresy wersji są zwykle ciągłe, ale mogą się też
nakładać.
Na przykład r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 i
r8-from-8.2.0 to nazwy katalogów reprezentujące zestaw docelowych reguł R8. Reguły w katalogu r8 mogą być używane przez dowolne wersje R8. Reguły w katalogu r8-from-8.0.0-upto-8.2.0 mogą być używane przez R8 w wersji od 8.0.0 do 8.2.0, ale bez wersji 8.2.0.
Wtyczka Androida do obsługi Gradle używa tych informacji do wybierania wszystkich reguł, które mogą być używane przez bieżącą wersję R8. Jeśli biblioteka nie określa docelowych reguł R8, wtyczka Androida do obsługi Gradle wybierze reguły z dotychczasowych lokalizacji
(proguard.txt w przypadku pliku AAR lub META-INF/proguard/<ProGuard-rule-files> w przypadku pliku
JAR).