System kompilacji platformy Androida (Soong) uruchamia kompilator R8 na elementach docelowych, które kompilują kod bajtowy DEX, w tym na aplikacjach na Androida (android_app), testach (android_test) i bibliotekach Javy z możliwością zainstalowania z compile_dex: true (takich jak services.jar), aby zmniejszyć, zoptymalizować i usunąć nieużywany kod i zasoby. W przypadku bibliotek statycznych (android_library i statycznych java_library) Soong nie uruchamia bezpośrednio R8, ale używa bloku właściwości optimize do dołączania i rozpowszechniania reguł zachowywania konsumentów (export_proguard_flags_files: true) do dalszych elementów docelowych DEX, które łączą je statycznie.
Inżynierowie platformy, którzy tworzą obrazy systemu lub pakiety dostawców, mogą odnieść bezpośrednie korzyści z eliminacji zbyt szerokich reguł zachowywania:
- Mniejszy rozmiar partycji systemowej: R8 usuwa nieużywane klasy, metody i zasoby przed spakowaniem plików APK i JAR do
/system,/system_ext,/producti/vendor. - Mniejsze skompilowane artefakty: mniejsza liczba metod DEX oznacza mniejsze pliki
.odexi.vdexgenerowane przezdex2oatpodczas kompilacji lub kompilacji na urządzeniu. - Mniejsze obciążenie pamięci wykonawczej: ponieważ Android mapuje pliki
.odex,.vdexi tabele zasobów pakietu APK na pamięć procesów za pomocą wywołańmmapstronicowania na żądanie, mniejsze pliki binarne zmniejszają liczbę poważnych błędów stron podczas uruchamiania aplikacji oraz zmniejszają ilość pamięci kodu i zasobów w procesach. Więcej informacji o tym, jak skompilowany kod aplikacji wpływa na pamięć urządzenia, znajdziesz w artykule Kod aplikacji to pamięć. - Bardziej efektywna optymalizacja całego programu: wąskie ograniczenia zachowywania pozwalają R8 na wstawianie metod, dewirtualizację interfejsu i wywołań wirtualnych, usuwanie nieużywanych pól i propagowanie stałych w klasach.
Konfigurowanie optymalizacji R8 w Soong
W plikach Android.bp skonfiguruj optymalizację R8 za pomocą bloku właściwości optimize w przypadku obiektów docelowych android_app i java_library z możliwością zainstalowania (lub w przypadku modułów android_library i statycznych java_library, aby wyeksportować reguły zachowywania konsumentów):
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
W tabeli poniżej znajdziesz podsumowanie najczęstszych właściwości optimize w Soong (zdefiniowanych w build/soong/java/dex.go):
| Właściwość | Opis |
|---|---|
enabled |
Określa, czy R8 działa w miejscu docelowym. Domyślna wartość to true dla wszystkich celów android_app.
|
shrink |
Steruje eliminacją martwego kodu, aby usunąć nieosiągalne klasy, pola i metody. Domyślnie true dla celów android_app (false dla samodzielnych modułów java_library i testowych, które przekazują -dontshrink, chyba że ustawiono inaczej).
|
optimize |
Kontroluje optymalizacje kodu bajtowego, takie jak wstawianie metod, scalanie klas, propagacja stałych i usuwanie martwych gałęzi. Domyślna wartość to true dla celów android_app (kontrolowana przez flagę kompilacji do publikacji RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
Kontroluje minifikację i zmianę nazw identyfikatorów. Domyślnie jest to false ze względu na zgodność z poprzednimi wersjami, ale ustaw true, gdy tylko jest to możliwe, aby zmniejszyć rozmiar DEX i odblokować głębsze optymalizacje (patrz Włącz zaciemnianie kodu, gdy tylko jest to możliwe).
|
shrink_resources |
Usuwa nieużywane zasoby (res/ wpisy) z pakietu APK po zmniejszeniu kodu. Domyślna wartość to false.
|
optimized_shrink_resources |
Uruchamia zintegrowany potok R8 do zmniejszania kodu i zasobów, dzięki czemu R8 śledzi odwołania do kodu i zasobów w jednym przebiegu. Gdy ustawiona jest wartość shrink_resources: true, domyślnie jest to RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true w standardowych kompilacjach platformy).
|
proguard_flags_files |
Zawiera listę plików .flags lub .pro powiązanych z modułem, które zawierają niestandardowe reguły zachowywania. Unikaj dodawania plików niestandardowych, gdy wystarczą standardowe adnotacje lub wartości domyślne.
|
export_proguard_flags_files |
Przekazuje proguard_flags_files tej biblioteki do modułów podrzędnych, które są od niej statycznie zależne.
|
trace_references_from |
Wyświetla listę bezpośrednich elementów docelowych zależnych od kompilacji DEX (połączonych przez instrumentation_for lub libs), których odwołania do kodu bajtowego w tym elemencie docelowym R8 automatycznie śledzi i zachowuje (są też propagowane w wariantach override_android_app i override_android_test oraz obsługiwane w przypadku niezależnych towarzyszących bibliotek Java, gdy są sparowane z podziałem .impl).
|
allow_traced_references |
Umożliwia bezpośrednie kompilowanie do DEX zależnych elementów docelowych (połączonych przez instrumentation_for lub libs, takich jak testy między repozytoriami lub testy dostawców, których nie można wymienić w trace_references_from), aby można było śledzić ich odwołania do kodu bajtowego w tym elemencie docelowym przez ustawienie trace_references: true.
|
trace_references |
Jeśli ten parametr ma wartość true w przypadku bezpośredniej zależności kompilacji DEX celu z allow_traced_references: true, uruchamia R8 tracereferences w tym module i przekazuje wygenerowane reguły zachowywania do modułu docelowego.
|
protect_api_surface |
Przekazuje --protect-api-surface do R8. Jeśli jest ustawiona na java_sdk_library, automatycznie generuje i dołącza reguły zachowywania interfejsu API Metalava we wszystkich włączonych zakresach interfejsu API (publicznym, systemowym, module-lib, system-server i testowym) do biblioteki implementacji.
|
Biblioteki statyczne (importy android_library, java_library i AAR) mogą też ustawić required_downstream_optimization: { enabled: true, optimize: true } w Android.bp, aby wymagać, żeby każdy docelowy element kompilacji DEX, który statycznie je łączy (bezpośrednio lub przejściowo), włączał kompresję R8 i optymalizację kodu bajtowego. Soong automatycznie włącza tę funkcję na platformie androidx.compose.runtime_runtime, dzięki czemu cele łączące Jetpack Compose są optymalizowane za pomocą R8.
Włącz zaciemnianie, gdzie tylko jest to możliwe.
W Soong obfuscate domyślnie ma wartość false ze względu na zgodność z poprzednimi wersjami, ale w miarę możliwości należy wyraźnie ustawić obfuscate: true na android_app i samodzielnych obiektach docelowych DEX:
- Mniejszy rozmiar plików DEX i
.vdex: zmiana nazw pakietów, klas, pól i metod na krótkie identyfikatory (a,b) zmniejsza pulę ciągów znaków DEX (string_idsistring_data_item) oraz deskryptory typów. Android mapuje pliki.vdexi.odexna pamięć procesu, więc mniejsze tabele symboli bezpośrednio zmniejszają rozmiar partycji systemowej i wykorzystanie pamięci w czasie działania. - Głębsze optymalizacje całego programu: umożliwienie R8 zmiany nazw identyfikatorów odblokowuje scalanie klas, spłaszczanie pakietów i deduplikację klas syntetycznych w pakietach, które w przeciwnym razie R8 musi pominąć, aby uniknąć kolizji nazw.
- Pełna symbolizacja zrzutu stosu: włączenie zaciemniania kodu w Soong nie zmniejsza możliwości debugowania. W przypadku każdego celu skompilowanego przez R8 Soong generuje plik mapowania
proguard_dictionaryw katalogu pośrednim modułu, grupuje wszystkie słowniki modułu w artefakt kompilacjiproguard-dict.zip, umieszcza skrót mapowania (--map-id-template) w nagłówku DEX i ponownie zapisuje atrybuty klasySourceFile(--source-file-template), abyretracemógł automatycznie symbolizować zrzuty stosu.
Zachowaj obfuscate: false (lub wyraźnie zachowaj nazwy publicznych interfejsów API) tylko w przypadku:
- Biblioteki udostępnione w ścieżce bootclasspath lub ścieżce klasy
system_server: moduły (java_librarylubjava_sdk_library), które udostępniają zewnętrzną powierzchnię interfejsu API połączoną dynamicznie przez inne moduły w czasie działania (np. celeframework.jar,services.jarlub<uses-library>), muszą zachowaćobfuscate: falselub wyraźnie zachować powierzchnię interfejsu API za pomocą regułprotect_api_surface: true,@KeepForApilub-keep, aby elementy wywołujące skompilowane na podstawie ich kodów pośredniczących mogły rozpoznawać nazwy klas i składowych w czasie działania. W modułachjava_sdk_libraryustawienieprotect_api_surface: trueautomatycznie generuje i dołącza reguły zachowywania Metalava ProGuard we wszystkich włączonych zakresach interfejsu API (publicznym, systemowym, module-lib, system-server i testowym) do biblioteki implementacji. Gdy uogólniona optymalizacja R8 jest włączona w przypadku nieaktualizowalnych elementów docelowych bootclasspath (framework-minus-apex,extiframework-location), zawierają one również plikframework-hiddenapi-proguard-rules(wygenerowany przezhiddenapi_to_proguard), aby zachować elementy wymienione w aktywnych plikachboot/hiddenapi/*.txt(z pominięciemhiddenapi-max-target-o.txt).
Zanim włączysz obfuscate: true w aplikacji, sprawdź te wymagania wstępne migracji:
- Zależności zewnętrznego testowego pliku APK: jeśli zewnętrzny plik APK
android_testustawiainstrumentation_for: "MyApp"i bezpośrednio wywołuje klasy wewnętrzne lub metodyMyApp, zmiana nazwy tych symboli powoduje błądNoSuchMethodErrorlubNoClassDefFoundErrorw czasie wykonywania testu. Skonfigurujtrace_references_from(luballow_traced_referencesztrace_references: true), utwórz test jako samodzielnie instrumentujący plik APK łączący statycznieMyApp.impllub dodaj adnotacje do punktów zaczepienia testu za pomocą@VisibleForTesting(patrz Krok 2. Przenieś reguły zachowywania oparte na testach), aby symbole wewnętrzne potrzebne do testów zostały zachowane podczas zaciemniania reszty aplikacji. - Refleksja oparta na ciągach znaków i JNI: jeśli moduł wyszukuje klasy, metody lub pola według nazw ciągów znaków (
Class.forName,getDeclaredMethodlub JNIFindClassiGetMethodID) bez reguł zachowywania lub adnotacji, zaciemnianie kodu zmienia nazwy tych elementów docelowych i uniemożliwia wyszukiwanie w czasie działania. (Pamiętaj, że nieoznaczona refleksja nie działa też wshrink: trueioptimize: true). Oznacz te punkty wejścia za pomocą przewodnika po adnotacjach Keep (@UsesReflection,@UsedByReflectionlub@UsedByNative), aby R8 zachował ich nazwy i zaciemnił resztę modułu.
Zasada braku niestandardowych flag
Idealny moduł platformy Androida nie zawiera niestandardowych plików proguard.flags ani keep.xml. W kompilacji platformy Androida zdecydowana większość niestandardowych reguł zachowywania jest nadmiarowa:
- Pliki bazowe platformy i AAPT2: większość punktów wejścia jest już automatycznie zachowywana przez globalne pliki bazowe platformy (takie jak
@Keep, metody JNInativei@VisibleForTesting) lub generowana przez AAPT2 zAndroidManifest.xmli zasobów układu (patrz Omówienie domyślnych reguł zachowywania platformy). - Ukierunkowane alternatywy: jeśli punkty wejścia muszą zostać zachowane, preferuj adnotacje w miejscu deklaracji (
keepanno,@VisibleForTesting), wyeksportowane reguły biblioteki (export_proguard_flags_files: true) lub statyczne testowe linkowanie zamiast odłączonych plików.flags(patrz Sprawdzanie i migracja istniejących reguł zachowywania).
Zanim dodasz lub zachowasz niestandardowy plik proguard.flags lub keep.xml, sprawdź, czy reguła jest już obsługiwana przez domyślne bazy lub czy można ją przenieść do adnotacji kodu.
Domyślne reguły przechowywania na platformie
Soong automatycznie przekazuje te globalne reguły podstawowe do każdego wywołania R8 w celach kompilacji DEX (android_app, android_test i moduły java_library z możliwością zainstalowania) skonfigurowanych w build/soong/java/dex.go:
build/make/core/proguard.flags: zachowuje klasy i elementy z adnotacją@com.android.internal.annotations.VisibleForTestingwe wszystkich pakietach oraz z adnotacją@VisibleForTesting(androidx.annotation.VisibleForTestinglubcom.google.common.annotations.VisibleForTesting) w pakietachandroid.**,com.android.**icom.google.android.**. Zachowuje też@TestApi,@Keep(androidx.annotation,android.support.annotation,com.android.internal.annotations),@KeepForWeakReference,@WeaklyReferencedCallbacki@dalvik.annotation.optimization.**.build/make/core/proguard_basic_keeps.flags: zachowuje atrybutySourceFiledla zrzutów stosu, adnotacje widoczności środowiska wykonawczego (RuntimeVisible*Annotations), atrybutyExceptionsiAnnotationDefault, metodynative, elementySerializable, metody@JavascriptInterface, konstruktoryThrowable(String), polaParcelable$CREATORi pola protobufMessageLite.build/make/core/proguard/kotlin.flags: wycisza niegroźne ostrzeżenia dotyczące określonych metaanotacji Kotlina (kotlin.Metadataikotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) i usuwa adnotacje KotlinaDebugMetadataw kompilacjach do publikacji.build/make/core/proguard/checknotnull.flags: zastępuje typowe wywołania pomocnicze sprawdzania wartości null (com.google.common.base.Preconditions.checkNotNullidagger.internal.Preconditions.checkNotNull*) zwięzłymi sprawdzeniami wartości null w kodzie bajtowym. Ten plik celowo pomijaObjects.requireNonNull, aby zachować komunikaty o wyjątkach w ramach interfejsu API platformy.build/make/core/proguard/enumvalues.flags: zachowuje metodyvaluesivalueOfw typachenum, chyba że zostały wyłączone w konfiguracji kompilacji.- Reguły wygenerowane automatycznie przez AAPT2: AAPT2 sprawdza scalone pliki
AndroidManifest.xml, pliki XML układu i pliki XML preferencji, aby wygenerować dokładne reguły zachowania dla każdego zarejestrowanego elementuActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, niestandardowej podklasyView, podklasyPreferencei metody XMLandroid:onClick.
Sprawdzanie i migracja istniejących reguł przechowywania
Podczas sprawdzania istniejących plików proguard.flags lub keep.xml w repozytorium platformy oceniaj każdą regułę po kolei według tej 4-stopniowej hierarchii:
Krok 1. Usuń zbędne lub nieaktualne reguły
Usuń reguły, które są już objęte globalnymi punktami odniesienia, regułami układu i pliku manifestu AAPT2 lub domyślnymi ustawieniami R8, a także reguły odwołujące się do klas lub pakietów, które już nie istnieją.
Jeśli wszystkie reguły w pliku .flags są nadmiarowe, usuń plik i usuń proguard_flags_files z Android.bp. Jeśli pozostały blok optimize tylko powtarza ustawienia domyślne, usuń go i sformatuj plik kompilacji za pomocą bpfmt -w Android.bp. Podczas czyszczenia pakietu sprawdź, czy moduły towarzyszące w podkatalogach (takie jak warianty docelowe Kotlin) odwołują się do tego samego pliku flag i zaktualizuj je razem.
Krok 2. Przenieś reguły przechowywania oparte na testach
Przenieś reguły zachowywania oparte na testach z niestandardowych plików produkcyjnych .flags, używając jednego z tych wzorców:
Śledzenie odwołań z celów testowych lub towarzyszących (
trace_references_fromluballow_traced_references): gdy zarówno cel, jak i bezpośrednio zależny moduł kompilują własny plik DEX (android_app,android_testlubjava_library) i są połączone za pomocąinstrumentation_forlublibs, Soong może uruchomić R8tracereferencesw zależnym module i przekazać wygenerowane reguły zachowania do kompilacji R8 celu bez tworzenia cyklu zależności ani konieczności ręcznego podziału biblioteki.impl:Testy w tym samym repozytorium (
trace_references_from): wyświetl listę bezpośrednich zależnościinstrumentation_forlublibsod modułu docelowego (jest to również automatycznie propagowane w przypadku wariantówoverride_android_appioverride_android_test, a także nadal obsługuje niezależne biblioteki towarzyszące Java, gdy są sparowane z podziałem.impl):android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }Testy między repozytoriami lub testy dostawcy (
allow_traced_referencesitrace_references): gdy moduł docelowy nie może wyświetlić zależności bezpośrednio wtrace_references_from(np. testy w innym repozytorium lub drzewie dostawcy), ustawallow_traced_references: truew module docelowym i wyraź zgodę z każdej zależności za pomocątrace_references: true:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }Refleksja oparta na ciągach znaków w testach:
tracereferencessprawdza odwołania do puli stałych kodu bajtowego i nie może wykryć refleksji opartej na ciągach znaków w testach (Class.forName,getDeclaredField,getDeclaredMethodani pomocników wstrzykiwania przez pola). Dodaj adnotacje do pól i metod produkcyjnych, do których dostęp jest uzyskiwany przez refleksję, za pomocą@VisibleForTesting(lub@UsedByReflection).
Połącz bibliotekę implementacji z testami (
static_libs): jeśli nie chcesz, aby w produkcyjnym pliku APK były zachowywane punkty wejścia tylko do testów, i nie chcesz, aby zmiany w testach ponownie uruchamiały R8 w celu produkcyjnym, umieść pliki źródłowe aplikacji wandroid_library(MyApp.impl) i statycznie połączMyApp.implzMyAppiMyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }Ten wzorzec z 3 modułami umożliwia uruchamianie
MyAppTestsjako testu z instrumentacją z pełnym dostępem do klas wewnętrznych, a jednocześnie pozwala na zmniejszanie i zaciemnianieMyAppwyłącznie w odniesieniu do produkcyjnych punktów wejścia.Oznaczanie punktów zaczepienia testu adnotacją
@VisibleForTesting: jeśli zewnętrzny testowy plik APK wywołuje tylko kilka metod wewnętrznych lub konstruktorów w celuandroid_app, możesz oznaczyć te deklaracje adnotacją@VisibleForTesting. Globalny plan bazowybuild/make/core/proguard.flagsautomatycznie zachowuje@VisibleForTestingelementy w pakietachandroid.**,com.android.**icom.google.android.**bez niestandardowych plików.flags. (W przypadku modułów dostawcy spoza tych przestrzeni nazw użyj regułtrace_references_from,@UsedByReflectionlub reguł wyeksportowanej biblioteki). Jeśli dodajesz adnotacje ręcznie za pomocą@VisibleForTestingzamiasttrace_references_fromlubtrace_references, dodaj też adnotacje do klas przekazywanych do Mockitomock(...)lubspy(...)(nie tylko do metod zastępczych), ponieważmockito-target(dexmaker-mockito) na urządzeniu generuje podklasy środowiska wykonawczego, a R8 może oznaczać klasy bez podklas jakofinal(MockitoException: Can't mock/spy class ... - final class).Unikaj nakładających się bibliotek statycznych lub przepakuj je (
jarjar_rules): gdy zarówno plik APKandroid_app, jak i testowy plik APKinstrumentation_forstatycznie łączą tę samą bibliotekę (bezpośrednio, np.guava, lub przejściowo),tracereferenceswidzi te klasy zdefiniowane w teście i nie generuje reguł zachowania dla aplikacji, a w czasie działania nadrzędny moduł ładujący klasy aplikacji przesłania kopię testu klasami zmniejszonymi przez R8, co powoduje błądIllegalAccessErrorlubNoSuchMethodError. Jeśli test nie potrzebuje własnej kopii pakietu, usuń duplikat z plikustatic_libstestu (lub połącz go za pomocąlibs), aby uniknąć kolizji. W przeciwnym razie przepakuj nakładające się pakiety bibliotek statycznych aplikacji pod prefiksem modułu za pomocąjarjar_rules.
Krok 3. Wyeksportuj reguły z biblioteki właściciela
Jeśli udostępniony element java_library lub android_library wymaga reguł zachowywania na potrzeby własnej wewnętrznej refleksji lub wywołań zwrotnych JNI, zdefiniuj reguły w docelowej bibliotece i ustaw export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
Wszystkie elementy podrzędne android_app i biblioteki, które są połączone statycznie z my-shared-library, automatycznie dziedziczą te reguły, więc aplikacje podrzędne nie muszą ich powielać. Gdy wiele modułów w różnych katalogach musi współdzielić zestaw reguł, który nie jest powiązany z jedną biblioteką kodu, opublikuj plik .flags za pomocą jawnego filegroup w Android.bp, aby moduły mogły odwoływać się do :my-shared-flags w sposób przejrzysty, niezależnie od granic pakietu. Ogólne wskazówki dotyczące tworzenia reguł przechowywania dla konsumentów biblioteki bez ograniczania optymalizacji aplikacji znajdziesz w artykule Optymalizacja dla autorów bibliotek.
Krok 4. Dodaj adnotacje do deklaracji w kodzie źródłowym
Gdy dostęp do klasy, metody, konstruktora lub pola jest uzyskiwany przez refleksję lub JNI i nie jest on objęty przez AAPT2 ani globalne bazy, zastąp odłączone reguły -keep w plikach .flags adnotacjami źródłowymi z przewodnika po adnotacjach Keep (zobacz dokumentację Javadoc keepanno):
@UsesReflection: używaj tej adnotacji, gdy kontrolujesz kod wykonujący refleksję. Umieść go w miejscu wywołania refleksji, aby zadeklarować, do których klas, metod lub pól docelowych uzyskiwany jest dostęp dynamiczny.@UsesReflectionautomatycznie koduje warunek wstępny, zgodnie z którym wywołanie z adnotacją musi być osiągalne, dlatego R8 usuwa zarówno wywołującego, jak i cel refleksyjny, jeśli wywołanie nie jest używane.@UsedByReflection: umieść w klasach, metodach, polach lub konstruktorach, które są tworzone lub wywoływane przez zewnętrzny kod lub biblioteki, takie jak klasy wczytywane za pomocąClass.forNamez kluczyBundlelubSettings, albo klasy wtyczek wczytywane przez dynamiczne moduły ładujące klasy. Określkind(np.KeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) i ograniczenia parametrów, aby R8 zachowywał tylko dokładną umowę dotyczącą refleksji i mógł nadal optymalizować lub usuwać nieużywane elementy klasy. Nie dodawaj@UsedByReflectiondo komponentów zarejestrowanych w pliku manifestu, takich jakJobServicelubBroadcastReceiver, ponieważ AAPT2 już je przechowuje.@UsedByNative: umieść w metodach lub polach, do których dostęp jest uzyskiwany z kodu JNI C lub C++ za pomocąGetMethodID,GetStaticMethodIDlubGetFieldID.@KeepForApi: umieść w klasach lub elementach interfejsu API biblioteki, które muszą pozostać nienaruszone, gdy biblioteka zostanie zmniejszona przed dystrybucją.@Keep(androidx.annotation.Keep): używaj jako opcji zastępczej, gdy nie można zastosowaćkeepanno. Zastosowanie reguły@Keepdo klasy powoduje bezwarunkowe zachowanie klasy i wszystkich jej elementów. Zastosowanie adnotacji@Keepdo metody lub pola działa jak bezwarunkowy punkt wejścia, który zachowuje element i zawierającą go klasę, nawet jeśli klasa nigdy nie zostanie utworzona, podczas gdy adnotacjakeepannowyraża warunkową osiągalność.
Aby używać adnotacji R8 keepanno w module Soong:
Dodaj
"keepanno-annotations"dolibsw:Android.bp:libs: [ "keepanno-annotations", ],Zaimportuj
com.android.tools.r8.keepanno.annotations.*i dodaj adnotację do deklaracji lub wywołania w kodzie źródłowym Javy lub Kotlina:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }R8 tłumaczy adnotacje
keepannobezpośrednio na wewnętrzny model reguł zachowywania i usuwa adnotacje z końcowego wyniku DEX, nie dodając żadnego narzutu na kod bajtowy w czasie działania.
Unikanie typowych pułapek
W sekcjach poniżej opisujemy częste błędy proguard.flags i keep.xml w kodzie platformy oraz sposoby ich naprawiania.
Reguły zachowywania komponentów przybliżonych
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Dlaczego obniża wydajność: ta reguła jest całkowicie zbędna w przypadku AAPT2.
Ponieważ używa symbolu wieloznacznego bez sprawdzania, czy komponent jest zarejestrowany w ostatecznym scalonym pliku
AndroidManifest.xml, wymusza na R8 zachowanie każdej podklasyActivity,ServicelubBroadcastReceiverznalezionej w dowolnym miejscu w ścieżce klasy, w tym nieużywanych komponentów biblioteki i wyłączonych aktywności debugowania. - Zalecane rozwiązanie: usuń regułę. AAPT2 sprawdza manifest łączony i generuje dokładne reguły
-keepdla zarejestrowanych komponentów.
Reguły zachowywania modułu obsługi kliknięć w szerokim formacie XML
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Dlaczego wpływa to negatywnie na wydajność: zachowanie każdej metody
public void *(View)we wszystkich klasach w module uniemożliwia R8 usunięcie lub wstawienie dowolnej metody, która akceptuje pojedynczy parametrView. - Zalecane rozwiązanie: usuń regułę. AAPT2 skanuje pliki XML układu i generuje ukierunkowane reguły zachowywania metod, do których odwołują się atrybuty
android:onClick.
Reguły zachowywania metod pobierających i ustawiających widoku Broad View
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- Dlaczego wpływa to negatywnie na wydajność: ta reguła wymusza na R8 zachowanie wszystkich metod pobierających i ustawiających w każdej podklasie
Vieww aplikacji i wszystkich połączonych bibliotekach (w tym bibliotekach AndroidX i Material), co uniemożliwia usuwanie nieużywanych metod i wstawianie ich w kodzie interfejsu. - Zalecane rozwiązanie: usuń regułę. AAPT2 zachowuje już konstruktory niestandardowych klas
Viewwyodrębnionych z plików XML układu. Jeśli kod animuje właściwość widoku za pomocą nazw ciągów znaków, takich jakObjectAnimator.ofFloat(view, "translationZ", ...), zastąp nazwę ciągu znaków odwołaniem do właściwości (View.TRANSLATION_Zlub niestandardową implementacjąFloatPropertylubIntProperty), aby całkowicie uniknąć refleksji. Jeśli nie można uniknąć dostępu do właściwości refleksyjnej, dodaj do konkretnej metody pobierającej lub ustawiającej adnotację@UsedByReflection.
Wyłączenie ogólnej optymalizacji lub zaciemniania kodu
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Dlaczego wpływa to negatywnie na wydajność: umieszczenie
-dontoptimizelub-dontshrinkw pliku.flagspowoduje niezauważalne zastąpienie ustawieńAndroid.bpmodułu i wyłączenie optymalizacji w całym środowisku docelowym. Co gorsza, jeśli biblioteka eksportuje plik.flagszawierający-dontoptimize, wyłącza optymalizację R8 dla każdego plikuandroid_app, który łączy się z biblioteką. - Zalecane rozwiązanie: usuń
-dontoptimize,-dontshrinki-dontobfuscatez.flagsplików. Kontroluj zachowanie optymalizacji bezpośrednio wAndroid.bp, używając blokuoptimize(shrink,optimize,obfuscate) w docelowym elemencie liścia.
Flagi diagnostyczne w zatwierdzonych regułach
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- Dlaczego to szkodzi kompilacji: flagi diagnostyczne zalewają logi kompilacji lub próbują zapisywać pliki wyjściowe w ścieżkach lokalnych podczas kompilacji Soong w piaskownicy.
- Zalecane rozwiązanie: usuń te flagi z zatwierdzonych plików
.flags. Soong automatycznie zapisuje dane wyjściowe mapowania i użycia R8, takie jakproguard_dictionaryiproguard_usage.zip, w katalogu pośrednim modułu wout/soong/.intermediates/.
Reguły zachowywania kodu testowego w wersji produkcyjnej
- Dlaczego wpływa to negatywnie na wydajność: dodanie niestandardowych reguł
-keepwyłącznie na potrzeby dostępu testowego powoduje, że kod pozostaje niezaciemniony i jest zachowywany w kompilacjach produkcyjnych. Podczas konfigurowaniatrace_references_from(luballow_traced_referencesztrace_references: true) lub dodawania adnotacji do metod za pomocą@VisibleForTestingunika się ręcznego utrzymywania reguł-keepi zachowuje tylko to, do czego odwołują się testy, ale te symbole nadal są dostarczane w pliku binarnym produkcyjnym. - Zalecane rozwiązanie: wyeliminuj niestandardowe reguły testowe
-keep, włączająctrace_references_from(luballow_traced_referencesitrace_references: true), albo podziel aplikację na bibliotekę.implpołączoną z samoinstrumentującym testem z instrumentacją w pliku APK za pomocąstatic_libs, aby punkty wejścia tylko do testów były całkowicie poza produkcyjnym plikiem APK, zgodnie z opisem w sekcji Krok 2. Przenieś reguły zachowywania oparte na testach.
Ogólne symbole wieloznaczne zasobów w pliku keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- Dlaczego wpływa to negatywnie na wydajność: symbole wieloznaczne zachowują wszystkie zasoby pasującego typu w module i jego zależnościach, co uniemożliwia zmniejszanie zasobów (
shrink_resources: true) i powoduje zwiększenie rozmiaru pliku APK oraz tabeli mapowaniaresources.arsc. - Zalecane rozwiązanie:
- Używaj odwołań do zasobów statycznych zamiast
keep.xml: unikaj używania metodyResources.getIdentifierdo dynamicznego wyszukiwania ograniczonego zbioru zasobów, takich jak numerowane ciągi eksperymentalne lub elementy graficzne o określonej tematyce. Zamiast tego użyj instrukcjiswitchw czasie kompilacji lub mapuj na stałe statyczneR.id,R.stringlubR.drawable. Statyczne odwołania umożliwiają R8 i AAPT2 śledzenie dokładnych zasobów aktywnych, eliminują narzut związany z wyszukiwaniem ciągów znaków w czasie działania i całkowicie eliminują potrzebę stosowaniakeep.xml. - Podaj nazwy konkretnych zasobów: jeśli wymagane jest dynamiczne wyszukiwanie, np. przez czytnik licencji innej firmy, podaj dokładne identyfikatory zasobów w polu
tools:keep(np.tools:keep="@raw/third_party_licenses"). - Usuń zbędne pliki
keep.xml: jeśli wymienione zasoby są już statycznie przywoływane w kodzie (R.raw.foo) lub pliku XML (@raw/foo), narzędzie do zmniejszania rozmiaru zachowuje je automatycznie, więc możesz usunąć plikres/raw/keep.xml.
- Używaj odwołań do zasobów statycznych zamiast
Weryfikowanie i audytowanie zmian reguł
Za każdym razem, gdy usuwasz lub zawężasz reguły zachowywania w pliku proguard.flags lub keep.xml, sprawdź, czy moduł kompiluje się bez błędów, przechodzi testy jednostkowe i testy z instrumentacją oraz zachowuje wszystkie wymagane punkty wejścia.
Tworzenie i testowanie modułów
Skompiluj moduł, aby sprawdzić, czy R8 i AAPT2 kończą działanie bez ostrzeżeń o braku odniesień:
m <MODULE_NAME>Uruchom testy jednostkowe i testy z instrumentacją dla modułu, używając polecenia
atest:atest <TEST_MODULE_NAME>W przypadku aplikacji systemowych, usług uprzywilejowanych lub modułów zależnych od sprzętu uruchom testy instrumentacji i interfejsu na docelowych urządzeniach fizycznych lub w laboratorium testowym urządzeń, aby sprawdzić ścieżki refleksji w czasie działania, powiązania IPC i inflację zasobów.
Sprawdzanie różnic w plikach DEX i zasobach
Porównaj skompilowany plik APK lub JAR przed zmianami reguł i po nich, aby sprawdzić, czy R8 usuwa nieużywany kod i zasoby bez usuwania oczekiwanych punktów wejścia:
Użyj
apkanalyzerlubdexdump, aby sprawdzić zachowane klasy, metody i pola w wyjściowych plikach APK lub DEX:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkPodczas modyfikowania
keep.xmllub włączaniashrink_resourcesużyjaapt2 dump resources, aby sprawdzić, czy kompilacja usuwa nieużywane zasoby i zachowuje wymagane:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkPodczas uruchamiania
prebuilts/r8/r8.jarbezpośrednio na moduleclasses.jarprzed R8 w celu sprawdzenia zmian flag offline bez pełnej kompilacji platformy przekaż zarówno pliki JAR ścieżki klasy--libmodułu (android.jar,framework.jari udostępnionylibs), jak i wygenerowane przez AAPT2 regułyproguard.optionszAndroidManifest.xmli plików XML układu. Bez tych danych wejściowych R8 nie może rozpoznać nadtypów platformy (Activity,Service,Phone,Handler) i nie zachowuje metod hierarchii.
Analizowanie reguł zachowywania i subsumpcji reguł za pomocą R8
Kompilator R8 typu open source zawiera analizator Keep Radius (dostępny też w Android Studio i Gradle jako analizator konfiguracji R8), który mierzy dokładny wpływ każdej reguły zachowywania podczas kompilacji. Soong integruje ten analizator bezpośrednio z kompilacją platformy Androida (skonfigurowaną w pliku build/soong/java/dex.go).
Aby przeanalizować reguły zachowywania dla modułu platformy lub całej kompilacji:
Uruchom kompilację ze zmienną środowiskową
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>Gdy ustawiona jest zmienna
R8_DUMP_KEEP_RADIUS=true, Soong instruuje R8, aby rejestrował dane reguł zachowywania w pośrednim plikur8keepradius.pbdla każdego skompilowanego modułu wout/soong/.intermediates/. W przypadku każdej reguły zachowywania i adnotacjikeepannoR8 rejestruje:- Bezpośredni zakres zachowania: dokładne klasy, pola i metody zachowywane przez regułę wraz z określonymi ograniczeniami, które wymusza ona w odniesieniu do zmniejszania, optymalizacji lub zaciemniania.
- Zastępowanie reguł: które inne reguły przechowywania lub adnotacje już przechowują te same elementy. Jeśli reguła niestandardowa jest całkowicie objęta regułą AAPT2 lub globalną regułą podstawową, możesz ją bezpiecznie usunąć.
- Reguły dotyczące całego pakietu i reguły globalne: reguły, które używają symboli wieloznacznych dotyczących całego pakietu lub stosują globalne dyrektywy konfiguracji.
Przekonwertuj dane wyjściowe
r8keepradius.pbna interaktywny raport HTML za pomocąKeepRadiusHtmlReportGenerator(w pakiecie zprebuilts/r8/r8.jar):# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsGdy podasz katalog,
KeepRadiusHtmlReportGeneratorprzechodzi przez wszystkie pliki*keepradius*.pb, generuje raport HTML dla każdego modułu i tworzy plikout/keep_radius_reports/keepradius.htmlpodsumowujący liczbę aktywnych elementów, liczbę zachowanych elementów i reguły zachowywania o największym promieniu w całej kompilacji. Więcej informacji o interpretowaniu wyników zmniejszania, optymalizacji i zaciemniania kodu w wygenerowanym raporcie znajdziesz w artykule Korzystanie z analizatora konfiguracji R8.
Soong obsługuje też dodatkowe zmienne środowiskowe diagnostyki R8 i pętli wewnętrznej w pliku build/soong/java/dex.go:
R8_DUMP_INPUT=true: zapisujer8inputs.zipw katalogu pośrednim modułu zawierającym wszystkie wejściowe pliki JAR, pliki JAR biblioteki i scalone konfiguracje ProGuard na potrzeby samodzielnego odtworzenia R8.R8_DUMP_PERFETTO_TRACE=true: zapisujer8trace.ptracew katalogu pośrednim modułu, aby umożliwić sprawdzanie przebiegów kompilacji R8 w Perfetto.SOONG_SKIP_TRACE_REFERENCES=true: pomija uruchamianie regułtracereferencesskonfigurowanych przeztrace_references_fromlubtrace_references, aby przyspieszyć lokalne kompilacje pętli wewnętrznej podczas iteracji kodu testowego.
Sprawdzanie reguł dotyczących klientów biblioteki
Gdy java_library lub android_library eksportuje reguły zachowywania do konsumentów na dalszych etapach (export_proguard_flags_files: true), reguły te nie mogą zawierać flag globalnych, które zmieniają lub wyłączają optymalizację aplikacji użytkownika.
R8 udostępnia analizator reguł zachowywania typu open source (ProcessKeepRules), który jest dostępny w kompilacji platformy Androida jako narzędzie hosta process-keep-rules (zdefiniowane w prebuilts/r8/Android.bp):
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
Narzędzie process-keep-rules analizuje każdy plik konfiguracji i w przypadku napotkania dyrektyw niedozwolonych w regułach konsumenta biblioteki kończy działanie, podając informacje diagnostyczne o pliku i wierszu. Niedozwolone dyrektywy obejmują wyłączenie globalnej optymalizacji, zmniejszania lub zaciemniania (np. -dontoptimize lub -dontshrink), flagi ponownego pakowania i modyfikacji dostępu, flagi diagnostyczne lub mapowania oraz -keepattributes na poziomie aplikacji.
Kontrola drzew źródłowych za pomocą pgaudit.py
Aby przeprowadzić kontrolę nieskompilowanych katalogów źródłowych wraz z analizatorami czasu kompilacji R8, drzewo platformy Androida zawiera skrypt pgaudit.py w build/make/core/proguard/tools/. To narzędzie do analizy statycznej skanuje pliki Android.bp, proguard.flags i keep.xml w repozytorium, aby oznaczyć reguły, które są już objęte przez AAPT2 lub globalne bazy platformy, szerokie symbole wieloznaczne, zastąpienia -dontoptimize i reguły zachowywania tylko do testów:
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
Zespoły ds. platformy i OEM mogą połączyć pgaudit.py, aby szybko przeprowadzić selekcję drzewa źródłowego, process-keep-rules, aby zweryfikować wyeksportowane reguły biblioteki, oraz R8_DUMP_KEEP_RADIUS=true z KeepRadiusHtmlReportGenerator, aby zmierzyć dokładny promień przechowywania klas, pól i metod każdej reguły w kompilacji.