Jako autor biblioteki musisz zadbać o to, aby deweloperzy aplikacji mogli łatwo włączyć ją do swoich aplikacji, zachowując przy tym wysoką jakość obsługi użytkowników. Oznacza to, że 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. Ważne 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ę, jak zoptymalizować aplikację na Androida, przeczytaj artykuł Włączanie optymalizacji aplikacji. Aby dowiedzieć się, które biblioteki są odpowiednie do użycia, przeczytaj artykuł Rozważny wybór bibliotek.
Informacje o typach reguł przechowywania
W bibliotekach możesz mieć 2 rodzaje reguł przechowywania:
- Reguły przechowywania konsumentów 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ć, jaki kod należy zachować. Biblioteki powinny zawierać reguły przechowywania konsumentów, które mają taki sam format jak reguły przechowywania aplikacji. Te reguły są dołączane do artefaktów biblioteki (AAR lub JAR) i są 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 konsumentów. - Reguły zachowywania kompilacji biblioteki są stosowane podczas kompilowania biblioteki. Są one potrzebne tylko wtedy, gdy zdecydujesz się częściowo zoptymalizować bibliotekę w momencie kompilacji. Muszą one zapobiegać usunięciu publicznego interfejsu API biblioteki, w przeciwnym razie nie będzie on obecny 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łą bibliotekę: 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 korzystających z Twojej biblioteki.
Nie uwzględniaj 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
-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 powtarzalnego kodu 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ąć.
W przypadku odbicia kod jest analizowany i modyfikowany w czasie działania. 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, co powoduje awarie aplikacji u użytkowników.
Wiele nowoczesnych bibliotek używa generowania kodu zamiast odbicia. Więcej informacji o KSP znajdziesz w sekcji dotyczącej wspólnego punktu wejścia używanego przez Room, Dagger2 i wiele innych bibliotek.
Obsługa trybu pełnego R8: biblioteka nie powinna ulegać awarii, 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
-repackageclassesw pliku reguł zachowywania dla konsumentów 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 na ogół konieczne, ponieważ aplikacje również 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
Gdy komentarz 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 ukierunkowanych reguł przechowywania dla konsumentów.
Ten konkretny i ukierunkowany rodzaj refleksji to wzorzec, który można zaobserwować zarówno w przypadku platformy Android (np. podczas powiększania aktywności, widoków i elementów rysowalnych), jak i bibliotek AndroidX (np. podczas tworzenia WorkManager
ListenableWorkers lub RoomDatabases). Z kolei otwarta refleksja Gson nie nadaje się do używania w aplikacjach na Androida.
Często występujące nieporozumienia
Niektóre powszechne błędne przekonania mogą prowadzić do nieprawidłowej konfiguracji R8. Należą do 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 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 podczas 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 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 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 biblioteki.
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 pod dowolną nazwą. Jeśli na przykład kod znajduje się w katalogu <libraryroot>/src/main/kotlin, umieść plik reguł konsumenta w katalogu <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro. Reguły zostaną dołączone w odpowiednim 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 kompilacji 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")
}
}
}
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. W najgorszym przypadku jest to Twój 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 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żywaj -repackageclasses
'com.example.mylibrary.internal' zamiast -repackageclasses 'internal'.
Obsługa różnych wersji R8 (zaawansowane)
Możesz dostosowywać reguły, aby kierować je na określone 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ć wiele podkatalogów o nazwach w formacie r8-from-<X>-upto-<Y>, które wskazują, dla których wersji R8 są przeznaczone 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, które mają być stosowane. 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 bez włącznie.
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 lokalizacji starszego typu (proguard.txt w przypadku pliku AAR lub META-INF/proguard/<ProGuard-rule-files> w przypadku pliku JAR).