Jako autor biblioteki musisz zadbać o to, aby deweloperzy aplikacji mogli łatwo włączyć ją do swoich aplikacji, zachowując jednocześnie wysoką jakość wrażeń użytkowników. Upewnij się, że Twoja biblioteka jest zgodna z optymalizacją Androida bez dodatkowej konfiguracji, lub udokumentuj, że może być nieodpowiednia do użycia na Androidzie.
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. Aby dowiedzieć się, które biblioteki są odpowiednie, przeczytaj artykuł Mądrze wybieraj biblioteki.
Używaj generowania kodu zamiast refleksji
Jeśli to możliwe, 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 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 programu, co powoduje awarie aplikacji u użytkowników.
Wiele nowoczesnych bibliotek korzysta z generowania kodu zamiast z odbicia. Więcej informacji znajdziesz w artykule o KSP, który jest powszechnym punktem wejścia używanym przez Room, Dagger2 i wiele innych bibliotek.
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.
Ta konkretna i ukierunkowana forma refleksji jest wzorcem, który można zaobserwować zarówno w przypadku platformy Android (np. podczas tworzenia aktywności, widoków i elementów rysowalnych), jak i bibliotek AndroidX (np. podczas tworzenia instancji klas WorkManager
ListenableWorkers lub RoomDatabases). Z kolei otwarta refleksja Gson nie nadaje się do używania w aplikacjach na Androida.
Typy reguł przechowywania w bibliotekach
W bibliotekach możesz mieć 2 rodzaje reguł przechowywania:
- Reguły przechowywania danych konsumenckich 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 zachowywania danych użytkowników, które mają taki sam format jak reguły zachowywania danych 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 kompilacji biblioteki są stosowane podczas kompilowania 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, ponieważ 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.
Tworzenie reguł przechowywania danych konsumenta
Oprócz ogólnych sprawdzonych metod dotyczących reguł zachowywania poniżej znajdziesz zalecenia skierowane do autorów bibliotek.
- Nie używaj nieodpowiednich reguł globalnych – unikaj umieszczania w pliku reguł keep biblioteki ustawień globalnych, takich jak
-dontobfuscatelub-allowaccessmodification, ponieważ mają one wpływ na wszystkie aplikacje, które korzystają z Twojej biblioteki. - Nie uwzględniaj reguł przechowywania dotyczących całego pakietu, takich jak
-keep class com.mylibrary.** { *; }. Takie reguły ograniczają optymalizację w całej bibliotece, co wpływa na rozmiar wszystkich aplikacji, które z niej korzystają. - Nie używaj znaku
-repackageclassesw pliku reguł przechowywania dla użytkownikó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, nawet jeśli aplikacje, które z niej korzystają, nie są zoptymalizowane, ale zwykle nie jest to konieczne, ponieważ aplikacje również powinny być zoptymalizowane. Więcej informacji o optymalizacji bibliotek znajdziesz w artykule Optymalizacja dla autorów bibliotek. - 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 zachowywania konsumentów, jeśli adnotacje są używane w czasie działania programu. - Autorzy bibliotek nie powinni używać w regułach zachowywania tych opcji globalnych:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-overloadaggressively-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
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") } ... }
Groovy
android { defaultConfig { consumerProguardFiles 'consumer-proguard-rules.pro' } ... }
Biblioteki JAR
Aby dołączyć reguły do biblioteki Kotlin/Java dostarczanej jako plik JAR, umieść plik reguł w katalogu META-INF/proguard/ w końcowym pliku JAR. Nazwa pliku może być dowolna.
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 reguły końcowych pakietów JAR są prawidłowe, upewniając się, że znajdują się one w katalogu META-INF/proguard.
Optymalizacja kompilacji biblioteki AAR (zaawansowane)
Zwykle nie musisz bezpośrednio optymalizować kompilacji biblioteki, ponieważ możliwe optymalizacje w czasie kompilacji biblioteki są bardzo ograniczone. Dopiero podczas tworzenia aplikacji, gdy biblioteka jest do niej dołączana, R8 może dowiedzieć się, jak używane są wszystkie metody biblioteki i jakie parametry są przekazywane. 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, 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") } } }
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 ma zostać zachowana podczas jej 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 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ć reguły R8, które mają być stosowane, musisz umieścić je w katalogu META-INF/com.android.tools wewnątrz classes.jar w pliku AAR lub w katalogu META-INF/com.android.tools w 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 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, 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 wersji od 8.0.0 do 8.2.0 (bez 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).