Jako autor biblioteki musisz zadbać o to, aby deweloperzy aplikacji mogli łatwo zintegrować Twoją bibliotekę z aplikacją, 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. Jeśli biblioteka może być nieodpowiednia do użycia na Androidzie, musisz to udokumentować. 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ł przechowywania
W bibliotekach możesz mieć 2 różne typy reguł przechowywania:
- Reguły przechowywania 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 lub kodu zdefiniowanego przez aplikację klienta, te reguły muszą opisywać, który kod należy zachować. Biblioteki powinny zawierać reguły przechowywania konsumenta, 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 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 sekcji Dodatkowe zalecenia. - Reguły przechowywania 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, ponieważ 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ł przechowywaniaautorzy bibliotek muszą przestrzegać określonych wymagań i uwzględnić dodatkowe wytyczne.
Przestrzeganie wymagań dotyczących optymalizacji
Niewydajność bibliotek w znacznym stopniu 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ą unikać naruszania tych wymagań.
Brak ogólnych reguł przechowywania ani reguł przechowywania dla całego pakietu: Twoja biblioteka nie może zawierać ogólnych reguł przechowywania, które zachowują większość kodu w bibliotece lub w innej bibliotece. Ogólne reguły przechowywania mogą rozwiązać problemy z awariami w krótkim okresie, ale zwiększają rozmiar aplikacji wszystkich aplikacji, które korzystają z Twojej biblioteki.
Nie umieszczaj reguł przechowywania 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 modyfikowany 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, Hilt i wiele innych.
Obsługa trybu pełnego R8: Twoja biblioteka nie powinna się zawieszać, gdy włączony jest tryb pełny R8. Tryb pełny R8 jest zalecanym trybem używania R8 i jest 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 dodatkowe zalecenia.
- Nie używaj
-repackageclassesw pliku reguł przechowywania konsumenta biblioteki. Aby jednak zoptymalizować kompilację biblioteki, możesz użyć-repackageclassesz wewnętrzną nazwą pakietu, np.<your.library.package>.internal, w pliku reguł przechowywania 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ł przechowywania 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ł przechowywania kompilacji biblioteki, a nie w pliku reguł przechowywania konsumenta biblioteki:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Jeśli adnotacje są używane w czasie działania, autorzy bibliotek powinni zachować atrybut
RuntimeVisibleAnnotationsw regułach przechowywania konsumenta. - Autorzy bibliotek nie powinni używać tych opcji globalnych w regułach przechowywania 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:
- konkretnych typów docelowych (konkretnych implementacji interfejsu lub podklas),
- kodu używającego 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ł przechowywania konsumenta.
Ta konkretna i docelowa forma odbicia to wzorzec, który można zobaczyć w
frameworku Androida (np. podczas wczytywania zasobów systemowych)
i w bibliotekach AndroidX (np. podczas tworzenia WorkManager
ListenableWorkers, lub RoomDatabases). Natomiast odbicie otwarte
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 scalania 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 podczas kompilowania biblioteki są ograniczone, a Twoja aplikacja nie powinna wyłączać optymalizacji biblioteki przez uwzględnienie jej w regule przechowywania. Więcej informacji znajdziesz w artykule Omówienie optymalizacji R8.
Nieprawidłowe zrozumienie opcji
-keepReguła-keepuniemożliwia R8 uruchamianie dowolnych etapów optymalizacji. Więcej informacji znajdziesz w artykule Wybieranie odpowiedniej opcji przechowywania.
Sprawdzanie jakości reguł przechowywania konsumenta
Aby sprawdzić, czy Twoja biblioteka nie uniemożliwia zbyt dużej optymalizacji kodu po osadzeniu w aplikacji, użyj przykładowej biblioteki lub aplikacji do testów integracyjnych. Włącz R8 i sprawdź ogólną jakość konfiguracji R8 aplikacji za pomocą analizatora konfiguracji R8.
Jeśli okaże się, że reguły przechowywania konsumenta ograniczają optymalizację dużych części kodu biblioteki, oznacza to, że wymagają one dopracowania. Zawęź reguły przechowywania konsumenta i przetestuj wszystkie aspekty biblioteki, których to dotyczy, w aplikacji z włączoną funkcją R8.
Konfigurowanie pakowania reguł
Aby reguły przechowywania konsumenta były stosowane prawidłowo, musisz je odpowiednio spakować w zależności od formatu biblioteki.
Biblioteki AAR
Aby dodać reguły przechowywania 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 katalogu <libraryroot>/src/main/kotlin, umieść plik reguł przechowywania konsumenta
w katalogu
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
a reguły zostaną dołączone w odpowiednim miejscu w wyjściowym pliku JAR.
Sprawdź, czy końcowy plik JAR prawidłowo zawiera 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 wieloma 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 dołączane do biblioteki, aby wpływać na optymalizacje, które będą wykonywane później, podczas kompilowania aplikacji korzystającej z Twojej biblioteki.
Jeśli np. biblioteka używa odbicia do tworzenia klas wewnętrznych, może być konieczne zdefiniowanie reguł przechowywania 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'.
Optymalizowanie wydajności interfejsu Compose
Jeśli Twoja biblioteka zawiera komponenty interfejsu Compose UI, zoptymalizuj ją zarówno pod kątem zmniejszania rozmiaru pliku binarnego za pomocą R8 (jak opisano wcześniej), jak i wydajności rekompozycji:
- Projektowanie stabilnych komponentów: aby kompilator mógł bezpiecznie pomijać komponenty, gdy dane wejściowe się nie zmieniły, skonfiguruj modele danych i konfiguracje bibliotek zgodnie z regułami stabilności. Więcej informacji znajdziesz w artykule Stabilność w Compose.
- Sprawdzanie danych kompilatora: użyj danych kompilatora Compose, aby sprawdzić, czy punkty wejścia biblioteki są oznaczone jako restartowalne i pomijalne. Minimalizuje to obciążenie związane z rekompozycją w aplikacjach użytkownika. Więcej informacji znajdziesz w artykule Diagnozowanie problemów ze stabilnością.
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 w pliku classes.jar 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> 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 docelowych reguł R8. Reguły w katalogu r8 mogą być używane przez wszystkie 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 AAR lub META-INF/proguard/<ProGuard-rule-files> w przypadku
JAR).