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ść wrażeń użytkowników. Oznacza to, że biblioteka musi być zgodna z optymalizacją pod kątem Androida (R8) bez konieczności dodatkowej konfiguracji przez dewelopera lub musi zawierać dokumentację, która wskazuje, że biblioteka może być nieodpowiednia do użycia na Androidzie. Kluczowe jest, aby biblioteki przeznaczone do użytku na Androidzie nie uniemożliwiały ważnych optymalizacji aplikacji i spełniały dodatkowe wymagania dotyczące optymalizacji.
Ta dokumentacja jest przeznaczona dla deweloperów opublikowanych bibliotek, ale może być też przydatna dla deweloperów wewnętrznych modułów bibliotecznych w dużej, modułowej aplikacji.
Jeśli jesteś deweloperem aplikacji i chcesz dowiedzieć się więcej o optymalizacji aplikacji na Androida, przeczytaj artykuł Włączanie optymalizacji aplikacji. Więcej informacji o tym, które biblioteki są odpowiednie, znajdziesz w artykule Rozważny wybór bibliotek.
Informacje o typach reguł przechowywania
W bibliotekach możesz mieć 2 rodzaje reguł przechowywania:
- Reguły przechowywania danych konsumenta muszą określać reguły, które zachowują wszystko, co odzwierciedla biblioteka. Jeśli biblioteka używa odbicia lub JNI do wywoływania kodu albo kodu zdefiniowanego przez aplikację klienta, te reguły muszą opisywać, który kod należy zachować. Biblioteki powinny zawierać reguły przechowywania dla konsumentów, które mają taki sam format jak reguły przechowywania aplikacji. Te reguły są dołączane do artefaktów biblioteki (plików AAR lub JAR) i automatycznie wykorzystywane podczas optymalizacji aplikacji na Androida, gdy biblioteka jest używana. Te reguły są przechowywane w pliku określonym za pomocą właściwości
consumerProguardFilesw plikubuild.gradle.kts(lubbuild.gradle). Więcej informacji znajdziesz w artykule Tworzenie reguł przechowywania dla konsumentów. - Reguły przechowywania podczas tworzenia biblioteki są stosowane podczas tworzenia biblioteki. Są one potrzebne tylko wtedy, gdy zdecydujesz się częściowo zoptymalizować bibliotekę w czasie kompilacji. Muszą oni zadbać o to, aby publiczny interfejs API biblioteki nie został usunięty. W przeciwnym razie nie będzie on dostępny w dystrybucji biblioteki, co oznacza, że deweloperzy aplikacji nie będą mogli z niej korzystać. Te reguły są przechowywane w pliku określonym za pomocą właściwości
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 korzystającej z tych bibliotek. Oprócz ogólnych sprawdzonych metod dotyczących reguł przechowywania autorzy bibliotek muszą przestrzegać określonych wymagań i uwzględniać dodatkowe wytyczne.
Przestrzegaj wymagań dotyczących optymalizacji
Nieefektywność bibliotek w dużej mierze przyczynia się do rozrostu aplikacji, marnowania pamięci, powolnego uruchamiania i błędów ANR (Aplikacja nie odpowiada). Aby uniknąć znacznego obniżenia jakości aplikacji i wygody użytkowników, biblioteki muszą przestrzegać tych wymagań:
Brak ogólnych reguł zachowywania obejmujących cały pakiet: 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 wszystkich aplikacji, które korzystają z Twojej biblioteki.
Nie uwzględniaj reguł przechowywania dotyczących 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
-dontobfuscatelub-allowaccessmodification.W miarę możliwości używaj generowania kodu zamiast odbicia: w miarę możliwości używaj generowania kodu (codegen) zamiast odbicia. Generowanie kodu i refleksja to popularne metody unikania kodu szablonowego podczas programowania, ale generowanie kodu jest bardziej kompatybilne z optymalizatorem aplikacji, takim jak R8.
W przypadku generowania kodu jest on analizowany i modyfikowany podczas procesu kompilacji. Ponieważ po kompilacji nie ma większych modyfikacji, optymalizator wie, który kod jest ostatecznie potrzebny, a który można bezpiecznie usunąć.
Dzięki odbiciu kod jest analizowany i modyfikowany w czasie wykonywania. Ponieważ kod nie jest ostateczny, dopóki nie zostanie wykonany, optymalizator nie wie, który kod można bezpiecznie usunąć. Prawdopodobnie usunie kod, który jest używany dynamicznie przez odbicie w czasie działania programu, co powoduje awarie aplikacji u użytkowników.
Wiele nowoczesnych bibliotek korzysta z generowania kodu zamiast z odbicia. Więcej informacji o często używanym punkcie wejścia, z którego korzystają Room, Dagger2 i wiele innych bibliotek, znajdziesz w sekcji KSP.
Obsługa trybu pełnego R8: biblioteka nie powinna się zamykać, gdy włączony jest tryb pełny R8. Pełny tryb R8 jest zalecanym trybem używania R8 i jest domyślny od wersji AGP 8.0, która została ustabilizowana w 2023 r. Jeśli biblioteka ulega awarii w R8, rozwiązaniem jest zidentyfikowanie konkretnego punktu wejścia odbicia lub JNI i dodanie ukierunkowanej reguły, a nie zachowanie całego pakietu.
Dodatkowe zalecenia
Oprócz wymagań dotyczących optymalizacji poniżej znajdziesz dodatkowe zalecenia.
- Nie używaj znaku
-repackageclassesw pliku reguł przechowywania dla konsumentów w bibliotece. 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, których biblioteka potrzebuje do działania, w plikach reguł zachowywania biblioteki, nawet jeśli mogą one pokrywać się z atrybutami zdefiniowanymi w
proguard-android-optimize.txt. - Jeśli w dystrybucji biblioteki wymagane są te atrybuty, zachowaj je w pliku reguł zachowywania kompilacji biblioteki, a nie w pliku reguł zachowywania konsumenta biblioteki:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Autorzy bibliotek powinni zachować atrybut
RuntimeVisibleAnnotationsw regułach przechowywania konsumentów, jeśli adnotacje są używane w czasie działania. - Autorzy bibliotek nie powinni używać w regułach przechowywania konsumenta tych globalnych opcji:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Kiedy odbicie jest w porządku
Jeśli musisz użyć odbicia, możesz to zrobić tylko w przypadku jednego z tych elementów:
- Określone typy docelowe (konkretne implementacje interfejsu lub podklasy)
- Kod z określoną adnotacją środowiska wykonawczego
Takie użycie refleksji ogranicza koszt w czasie działania i umożliwia pisanie reguł przechowywania kierowanych na konsumentów.
Ten konkretny i ukierunkowany rodzaj refleksji to wzorzec, który można zaobserwować zarówno w ramach Androida (np. podczas tworzenia aktywności, widoków i elementów rysowalnych), jak i w bibliotekach AndroidX (np. podczas tworzenia instancji klas WorkManager
ListenableWorkers lub RoomDatabases). Z kolei otwarta refleksja 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. Obejmują one:
Nieprawidłowe rozumienie optymalizacji R8: wbrew powszechnemu przekonaniu optymalizacje R8 nie ograniczają się tylko do zaciemniania kodu, ale obejmują też jego zmniejszanie i optymalizacje logiczne z technikami wstawiania metod i scalania klas. Więcej informacji znajdziesz w omówieniu optymalizacji R8.
Pomijanie optymalizacji zaciemnionych bibliotek: częstym błędem jest pomijanie optymalizacji biblioteki, ponieważ została ona zoptymalizowana lub zaciemniona podczas kompilacji do pliku AAR (Android Archive) lub JAR (Java Archive). Optymalizacje w czasie tworzenia biblioteki są ograniczone, a aplikacja nie powinna wyłączać optymalizacji biblioteki, uwzględniając ją w regule zachowywania. Więcej informacji znajdziesz w artykule Optymalizacja kompilacji biblioteki AAR.
Nieprawidłowe zrozumienie opcji
-keepReguła-keepuniemożliwia R8 uruchomienie jakichkolwiek etapów optymalizacji. Więcej informacji znajdziesz w artykule Wybieranie odpowiedniej opcji przechowywania.
Konfigurowanie pakowania reguł
Aby reguły przechowywania danych konsumentów były stosowane prawidłowo, musisz je odpowiednio spakować w zależności od formatu biblioteki.
Biblioteki AAR
Aby dodać reguły konsumenta dla biblioteki AAR, użyj opcji consumerProguardFiles w skrypcie kompilacji modułu biblioteki na Androida. Więcej informacji znajdziesz w naszych wskazówkach dotyczących tworzenia modułów biblioteki.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Biblioteki JAR
Aby dołączyć reguły do biblioteki Kotlin lub Java dostarczanej jako plik JAR, umieść plik reguł w katalogu META-INF/proguard/ w końcowym pliku JAR. Może on mieć dowolną nazwę.
Jeśli na przykład Twój kod znajduje się w <libraryroot>/src/main/kotlin, umieść plik reguł dotyczących konsumentów w <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro, a reguły zostaną dołączone we właściwym miejscu w wyjściowym pliku JAR.
Sprawdź, czy końcowe pakiety JAR są prawidłowe, upewniając się, że reguły znajdują się w katalogu META-INF/proguard.
Optymalizacja kompilacji biblioteki AAR (zaawansowana)
Zwykle nie musisz bezpośrednio optymalizować kompilacji biblioteki, ponieważ możliwe optymalizacje w czasie kompilacji biblioteki są bardzo ograniczone. Jako deweloper biblioteki musisz rozważyć wiele etapów optymalizacji i zachować spójność działania zarówno w czasie tworzenia biblioteki, jak i aplikacji, zanim zoptymalizujesz bibliotekę.
Jeśli nadal chcesz optymalizować bibliotekę w czasie kompilacji, jest to obsługiwane przez wtyczkę Androida do obsługi Gradle.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Pamiętaj, że działanie funkcji proguardFiles bardzo różni się od działania funkcji 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. Jest to co najmniej publiczny interfejs API.consumerProguardFilessą natomiast pakowane w bibliotece, aby wpływać na optymalizacje, które będą przeprowadzane później, podczas kompilowania aplikacji korzystającej z Twojej biblioteki.
Jeśli na przykład Twoja biblioteka używa odbicia do tworzenia klas wewnętrznych, może być konieczne zdefiniowanie reguł zachowywania zarówno w proguardFiles, jak i w consumerProguardFiles.
Jeśli w kompilacji biblioteki używasz -repackageclasses, zmień pakiet klas na podpakiet wewnątrz pakietu biblioteki. Na przykład użyj zasady -repackageclasses
'com.example.mylibrary.internal' zamiast -repackageclasses 'internal'.
Obsługa różnych wersji R8 (zaawansowane)
Możesz dostosowywać reguły tak, aby kierować je na konkretne wersje R8. Dzięki temu biblioteka będzie optymalnie działać w projektach, które korzystają z 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 umieścić je w katalogu META-INF/com.android.tools w 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 być kilka podkatalogów o nazwach w formacie r8-from-<X>-upto-<Y>, które wskazują, 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 reguł R8, do których kierowane są działania. Z zasadami w katalogu r8 można korzystać w dowolnych wersjach R8. Reguły w katalogu r8-from-8.0.0-upto-8.2.0 mogą być używane przez R8 w wersjach od 8.0.0 do 8.2.0 bez włączenia tej ostatniej.
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 reguł R8, wtyczka Androida do Gradle wybierze reguły z lokalizacji starszego typu (proguard.txt w przypadku pliku AAR lub META-INF/proguard/<ProGuard-rule-files> w przypadku pliku JAR).