System kompilacji platformy Android (Soong) uruchamia kompilator R8 na elementach docelowych, które kompilują kod bajtowy DEX, w tym aplikacje na Androida (android_app), testy (android_test) i biblioteki Java z możliwością zainstalowania z compile_dex: true (np. services.jar), aby zmniejszyć rozmiar, zoptymalizować i usunąć nieużywany kod i zasoby. W przypadku bibliotek statycznych (android_library i statycznych java_library) Soong nie uruchamia R8 bezpośrednio, ale używa bloku właściwości optimize, aby dołączać i propagować reguły zachowywania konsumenta (export_proguard_flags_files: true) do docelowych plików DEX, które są z nimi statycznie połączone.
W przypadku inżynierów platformy tworzących obrazy systemu lub pakiety dostawców wyeliminowanie zbyt ogólnych reguł zachowywania przynosi bezpośrednie korzyści dla kondycji systemu:
- Mniejszy rozmiar partycji systemowej: R8 usuwa nieużywane klasy, metody i zasoby przed spakowaniem plików APK i JAR na
/system,/system_ext,/producti/vendor. - Mniejsze skompilowane artefakty: mniej metod DEX oznacza mniejsze pliki
.odexi.vdexgenerowane przezdex2oatpodczas kompilacji w czasie kompilacji lub na urządzeniu. - Mniejsze obciążenie pamięci w czasie działania: ponieważ Android mapuje tabele zasobów
.odex,.vdexi APK do pamięci procesu za pomocą wywołańmmapz pamięcią stronicowaną na żądanie, mniejsze pliki binarne ograniczają poważne błędy strony podczas uruchamiania aplikacji i zmniejszają pamięć kodu rezydentnego 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ęć. - Skuteczniejsza optymalizacja całego programu: wąskie ograniczenia dotyczące zachowywania umożliwiają R8 wstawianie metod, devirtualizację wywołań interfejsów 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 celów android_app i instalowanych java_library (lub w przypadku modułów android_library i statycznych java_library, aby wyeksportować reguły zachowywania dla 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 ma być uruchamiany na urządzeniu docelowym. Domyślna wartość to true
w przypadku wszystkich android_app.
|
shrink |
Steruje usuwaniem nieużywanych klas, pól i metod. Domyślnie przyjmuje wartość true w przypadku android_app
elementów docelowych (false w przypadku samodzielnych java_library i modułów testowych, które przekazują -dontshrink, chyba że zostanie to wyraźnie
ustawione).
|
optimize |
Określa optymalizacje kodu bajtowego, takie jak wstawianie metod, scalanie klas, propagacja stałych i usuwanie nieużywanych gałęzi. Wartość domyślna to true w przypadku android_app (kontrolowana przez flagę kompilacji do publikacji RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
Kontroluje minifikację i zmianę nazw identyfikatorów. Domyślnie jest ustawiona wartość false ze względu na zgodność historyczną, ale w miarę możliwości ustawiaj wartość true, aby zmniejszyć rozmiar pliku DEX i odblokować bardziej zaawansowane optymalizacje (patrz Włączanie zaciemniania w miarę możliwości).
|
shrink_resources |
Usuwa nieużywane zasoby (wpisy res/) z spakowanego pliku 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 kod i odwołania do zasobów w jednym przebiegu. Gdy ustawiona jest wartość shrink_resources: true, domyślnie używana jest wartość RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT
(true w przypadku standardowych wersji platformy).
|
proguard_flags_files |
Zawiera listę plików .flags lub .pro specyficznych dla modułu, które zawierają niestandardowe reguły przechowywania. Unikaj dodawania plików niestandardowych, jeśli wystarczą standardowe adnotacje lub wartości domyślne.
|
export_proguard_flags_files |
Przekazuje poziom proguard_flags_files tej biblioteki do modułów podrzędnych, które są od niej statycznie zależne.
|
trace_references_from |
Zawiera listę celów towarzyszących bibliotece Java, których kod bajtowy odwołuje się do tego celu. R8 automatycznie śledzi i zachowuje te cele. |
Włączaj zaciemnianie wszędzie tam, gdzie to możliwe.
W Soong domyślnie używana jest wartość obfuscate false, aby zachować zgodność z wcześniejszymi wersjami, ale w miarę możliwości należy jawnie ustawić wartość obfuscate: true w przypadku android_app i samodzielnych elementów DEX:
- Mniejszy DEX i
.vdexrozmiar: zmiana nazw pakietów, klas, pól i metod na krótkie identyfikatory (a,b) zmniejsza pulę ciągów DEX (string_idsistring_data_item) oraz deskryptory typów. Ponieważ Android mapuje pliki.vdexi.odexdo pamięci procesu, mniejsze tablice symboli bezpośrednio zmniejszają rozmiar partycji systemowej i wykorzystanie pamięci w czasie działania. - Bardziej zaawansowane optymalizacje całego programu: umożliwienie R8 zmiany nazw identyfikatorów odblokowuje scalanie klas, spłaszczanie pakietów i usuwanie duplikatów klas syntetycznych w pakietach, które w przeciwnym razie R8 musiałby pominąć, aby uniknąć konfliktów nazw.
- Symbolizacja pełnego zrzutu stosu: włączenie zaciemniania w Soong nie zmniejsza możliwości debugowania. W przypadku każdego skompilowanego elementu docelowego R8 Soong generuje plik mapowania
proguard_dictionaryw katalogu pośrednim modułu, pakuje wszystkie słowniki modułu do artefaktuproguard-dict.zipkompilacji, osadza w nagłówku DEX skrót mapowania (--map-id-template) i przepisuje atrybuty klasySourceFile(--source-file-template), abyretracemogło automatycznie symbolizować ślady stosu.
Zachowaj obfuscate: false (lub wyraźnie zachowaj nazwy publicznych interfejsów API) tylko w przypadku:
- Biblioteki współdzielone na ścieżce rozruchowej lub ścieżce klasy
system_server: moduły (java_librarylubjava_sdk_library), które udostępniają zewnętrzny interfejs API, są łączone dynamicznie przez inne moduły w czasie wykonywania (np. elementy doceloweframework.jar,services.jarlub<uses-library>). Muszą one zachowaćobfuscate: falselub jawnie zachować swój interfejs API za pomocą reguł@KeepForApilub-keep(wraz zprotect_api_surface: truew przypadku elementów docelowych ścieżki rozruchowej), aby wywołujący skompilowani na podstawie ich stubów mogli rozwiązywać nazwy klas i elementów w czasie wykonywania.
Zanim włączysz obfuscate: true w aplikacji, sprawdź, czy spełniasz te wymagania wstępne migracji:
- Zależności zewnętrzne testowego pliku APK: jeśli zewnętrzny
android_testzestaw plików APKinstrumentation_for: "MyApp"bezpośrednio wywołuje wewnętrzne klasy lub metodyMyApp, zmiana nazw tych symboli powodujeNoSuchMethodErrorlubNoClassDefFoundErrorw czasie działania testu. Zalecamy skonfigurowanie testu jako samodzielnie instrumentującego pliku APK, któryMyApp.implstatycznie łączy, dodanie do punktów zaczepienia testu adnotacji@VisibleForTestinglub skonfigurowanietrace_references_from(patrz krok 2. Migracja reguł zachowywania opartych na testach), aby wewnętrzne symbole potrzebne do testów były zachowywane, a pozostała część aplikacji była zaciemniana. - Odbicie oparte na ciągach znaków i JNI: jeśli moduł wyszukuje klasy, metody lub pola według dosłownych nazw ciągów znaków (
Class.forName,getDeclaredMethodlub JNIFindClassiGetMethodID) bez reguł zachowywania lub adnotacji, zaciemnianie zmienia nazwy tych elementów i przerywa wyszukiwanie w czasie działania. (Pamiętaj, że nieoznaczone odbicie również nie działa w przypadkushrink: 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.
Stosuj zasadę braku niestandardowych flag
Idealny moduł platformy Android nie zawiera niestandardowych plików proguard.flags ani keep.xml. W kompilacji platformy Android większość niestandardowych reguł zachowywania jest zbędna:
- Podstawowe ustawienia platformy i AAPT2: większość punktów wejścia jest już automatycznie zachowywana przez globalne podstawowe ustawienia platformy (np.
@Keep, metody JNInativei@VisibleForTesting) lub generowana przez AAPT2 na podstawieAndroidManifest.xmli zasobów układu (więcej informacji znajdziesz w artykule Wyjaśnienie domyślnych reguł zachowywania platformy). - Alternatywne rozwiązania ukierunkowane: w przypadku konieczności zachowania punktów wejścia preferuj adnotacje w pliku deklaracji (
keepanno,@VisibleForTesting), wyeksportowane reguły biblioteki (export_proguard_flags_files: true) lub statyczne łączenie testów 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 wartości bazowe lub czy można ją przenieść do adnotacji w kodzie.
Domyślne reguły przechowywania na platformie
Soong automatycznie przekazuje te globalne reguły podstawowe do każdego wywołania R8 w przypadku docelowych elementów kompilacji DEX (android_app, android_test i instalowane moduły java_library) skonfigurowanych w build/soong/java/dex.go:
build/make/core/proguard.flags: zachowuje klasy i elementy oznaczone adnotacją@com.android.internal.annotations.VisibleForTestingwe wszystkich pakietach oraz oznaczone 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 atrybutySourceFilezrzutów stosu, adnotacje dotyczące widoczności w czasie działania (RuntimeVisible*Annotations), atrybutyExceptionsiAnnotationDefault, metodynative, elementySerializable, metody@JavascriptInterface, konstruktoryThrowable(String), polaParcelable$CREATORi polaMessageLiteprotokołu protobuf.build/make/core/proguard/kotlin.flags: wycisza nieszkodliwe ostrzeżenia dotyczące określonych metaanotacji języka Kotlin (kotlin.Metadataikotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) oraz usuwa anotacjeDebugMetadataw wersjach produkcyjnych.build/make/core/proguard/checknotnull.flags: zastępuje typowe wywołania pomocnicze sprawdzające wartość null (com.google.common.base.Preconditions.checkNotNullidagger.internal.Preconditions.checkNotNull*) zwięzłymi sprawdzaniami wartości null w kodzie bajtowym. W tym pliku celowo pominiętoObjects.requireNonNull, aby zachować wyraźne komunikaty o wyjątkach w ramach granic interfejsu API platformy.build/make/core/proguard/enumvalues.flags: zachowuje metodyvaluesivalueOfw przypadku typówenum, chyba że zostaną wyłączone przez konfigurację kompilacji.- Automatycznie generowane reguły AAPT2: AAPT2 sprawdza scalone pliki
AndroidManifest.xml, XML układu i XML preferencji, aby wygenerować dokładne reguły zachowywania dla każdego zarejestrowanego elementuActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, niestandardowej podklasyView, podklasyPreferencei metody XMLandroid:onClick.
Sprawdzanie i przenoszenie dotychczasowych reguł przechowywania
Podczas sprawdzania istniejących plików proguard.flags lub keep.xml w repozytorium platformy oceń każdą regułę w kolejności zgodnie z tą 4-stopniową hierarchią:
Krok 1. Usuń zbędne lub przestarzałe reguły
Usuń reguły, które są już objęte globalnymi wartościami bazowymi, regułami manifestu i układu 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ą zbędne, 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 (np. 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 przechowywania oparte na testach z niestandardowych plików produkcyjnych .flags, używając jednego z tych wzorców:
Połącz bibliotekę implementacji z testami (
static_libs): gdy testandroid_testkorzysta ze szczegółów implementacji aplikacji, które są prywatne w pakiecie lub wewnętrzne, zalecana architektura platformy polega na umieszczeniu plików źródłowych aplikacji wandroid_library(MyApp.impl) i statycznym połączeniuMyApp.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 3-modułowy wzorzec umożliwia uruchamianie modułu
MyAppTestsjako testu z samodzielnym instrumentowaniem z pełnym dostępem do klas wewnętrznych, pozwala modułowiMyAppswobodnie włączać modułobfuscate: truebez przerywania testów, zapobiega wysyłaniu punktów wejścia tylko do testów w APK produkcyjnym i eliminuje konieczność ponownego kompilowania modułuMyApppo zmianie kodu testowego.Dodaj adnotacje do punktów zaczepienia testu za pomocą
@VisibleForTesting: jeśli zewnętrzny plik APK testu wywołuje niewielką liczbę wewnętrznych metod lub konstruktorów wandroid_appi restrukturyzacja w bibliotekę.impljest niepraktyczna, dodaj do tych deklaracji adnotacje@VisibleForTesting. Globalnabuild/make/core/proguard.flagslinia bazowa zachowuje@VisibleForTestingelementy w pakietachandroid.**,com.android.**icom.google.android.**automatycznie bez niestandardowych.flagsplików. (W przypadku modułów dostawców spoza tych przestrzeni nazw używaj reguł@UsedByReflectionlub wyeksportowanych bibliotek).Używaj
trace_references_fromw różnych bibliotekach platformy: jeśli testy nie mogą statycznie połączyć implementacji docelowej (np. testy korzystające z usług serwera systemowego lub plików JAR platformy, takich jakframework-connectivity), skonfigurujtrace_references_fromw module docelowym, aby wskazywał moduł towarzyszący biblioteki Java zawierający źródła testowe. R8 śledzi i zachowuje wszystkie klasy i elementy, do których odwołuje się kod bajtowy towarzyszący.
Krok 3. Eksportowanie reguł z biblioteki będącej właścicielem
Jeśli biblioteka współdzielona java_library lub android_library wymaga reguł przechowywania na potrzeby własnych wewnętrznych odbić lub wywołań zwrotnych JNI, zdefiniuj reguły w przypadku docelowej biblioteki 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 dalsze cele android_app i cele biblioteki, które są statycznie połączonemy-shared-library, automatycznie dziedziczą te reguły, więc aplikacje podrzędne nie muszą ich duplikować. Jeśli 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 się odwoływać do :my-shared-flags w sposób przejrzysty w ramach granic pakietu. Ogólne wskazówki dotyczące tworzenia reguł przechowywania konsumenta biblioteki bez ograniczania optymalizacji aplikacji podrzędnej znajdziesz w artykule Optymalizacja dla autorów bibliotek.
Krok 4. Dodaj adnotacje do deklaracji w kodzie źródłowym
Gdy klasa, metoda, konstruktor lub pole są dostępne przez odbicie lub JNI i nie są objęte AAPT2 ani globalnymi wartościami bazowymi, zastąp odłączone reguły -keep w plikach .flags adnotacjami źródłowymi z przewodnika po adnotacjach Keep (patrz keepanno dokumentacja Javadoc):
@UsesReflection: używaj tej adnotacji, gdy masz kontrolę nad kodem wykonującym odbicie. Umieść go w miejscu wywołania refleksji, aby zadeklarować, do których klas, metod lub pól docelowych uzyskiwany jest dostęp dynamiczny. Ponieważ adnotacja@UsesReflectionautomatycznie koduje warunek wstępny, że sam adnotowany punkt wywołania jest osiągalny, R8 usuwa zarówno wywołującego, jak i odzwierciedlony cel, jeśli punkt wywołania nie jest używany.@UsedByReflection: umieść w klasach, metodach, polach lub konstruktorach, które są tworzone lub wywoływane przez kod lub biblioteki zewnętrzne, np. klasy wczytywane za pomocąClass.forNamez kluczyBundlelubSettingsalbo klasy wtyczek wczytywane w dynamicznych programach wczytujących klasy. Określkind(np.KeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) i ograniczenia parametrów, aby R8 zachowywał tylko dokładny kontrakt odbicia i mógł nadal optymalizować lub usuwać nieużywane elementy klasy. Nie dodawaj@UsedByReflectiondo komponentów zarejestrowanych w pliku manifestu, takich jakJobServiceczyBroadcastReceiver, ponieważ AAPT2 już je przechowuje.@UsedByNative: umieść w metodach lub polach, do których uzyskuje się dostęp z kodu JNI w 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żyj jako wartości domyślnej, gdykeepannonie ma zastosowania. Zastosowanie@Keepdo klasy powoduje bezwarunkowe zachowanie klasy i wszystkich jej elementów. Zastosowanie adnotacji@Keepdo metody lub pola działa jako bezwarunkowy punkt wejścia, który zachowuje element i jego klasę zawierającą, nawet jeśli klasa nigdy nie jest tworzona, natomiast adnotacjakeepannowyraża warunkową dostępność.
Aby używać adnotacji R8 keepanno w module Soong:
Dodaj
"keepanno-annotations"dolibswAndroid.bp:libs: [ "keepanno-annotations", ],Zaimportuj
com.android.tools.r8.keepanno.annotations.*i dodaj adnotację do deklaracji lub miejsca wywołania w kodzie źródłowym w Javie lub Kotlinie: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ńcowych danych wyjściowych DEX, dodając zero narzutu na kod bajtowy w czasie działania.
Unikanie typowych pułapek
W sekcjach poniżej opisujemy częste pułapki w kodzie platformy proguard.flags i keep.xml oraz sposoby ich eliminowania.
Ogólne reguły dotyczące przechowywania komponentów
# 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 skuteczność: ta reguła jest całkowicie zbędna w przypadku AAPT2.
Ponieważ używa symbolu wieloznacznego bez sprawdzania, czy komponent jest zarejestrowany w ostatecznej scalonej
AndroidManifest.xml, wymusza na R8 zachowanie każdej podklasyActivity,ServicelubBroadcastReceiverznalezionej w dowolnym miejscu ścieżki klasy, w tym nieużywanych komponentów biblioteki i wyłączonych działań debugowania. - Zalecane rozwiązanie: usuń regułę. AAPT2 sprawdza manifest łączony i generuje dokładne reguły
-keepdla zarejestrowanych komponentów.
Reguły zachowywania przybliżonego modułu obsługi kliknięć XML
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Dlaczego to obniża wydajność: zachowanie każdej metody
public void *(View)w każdej klasie w module uniemożliwia R8 usunięcie lub wstawienie dowolnej metody, która akceptuje pojedynczy parametrView. - Zalecane rozwiązanie: usuń regułę. Narzędzie AAPT2 skanuje pliki XML układu i generuje ukierunkowane reguły zachowywania dla metod, do których odwołują się atrybuty
android:onClick.
Reguły zachowywania funkcji pobierających i ustawiających w widoku ogólnym
# 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 ta reguła obniża wydajność: ta reguła wymusza zachowanie przez R8 wszystkich metod pobierających i ustawiających we wszystkich podklasach
Vieww aplikacji i we wszystkich połączonych bibliotekach (w tym bibliotekach AndroidX i Material), co uniemożliwia usuwanie nieużywanych metod i wstawianie kodu w kodzie interfejsu. - Zalecane rozwiązanie: usuń regułę. AAPT2 zachowuje już konstruktory w przypadku niestandardowych klas
Viewwczytywanych z plików XML układu. Jeśli kod animuje właściwość widoku za pomocą nazw ciągów znaków opartych na odbiciu, takich jakObjectAnimator.ofFloat(view, "translationZ", ...), zastąp nazwę ciągu znaków odwołaniem do właściwości z określonym typem (View.TRANSLATION_Zlub niestandardową implementacjąFloatPropertyalboIntProperty), aby całkowicie uniknąć odbicia. Jeśli nie można uniknąć dostępu do właściwości za pomocą refleksji, oznacz konkretny getter lub setter adnotacją@UsedByReflection.
Wyłączanie optymalizacji lub zaciemniania
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Dlaczego wpływa to na wydajność: umieszczenie
-dontoptimizelub-dontshrinkw pliku.flagscicho zastępuje ustawienia modułuAndroid.bpi wyłącza optymalizację w całym obszarze docelowym. Co gorsza, jeśli biblioteka eksportuje plik.flagszawierający-dontoptimize, wyłącza optymalizację R8 w przypadku każdego kolejnego modułuandroid_app, który łączy bibliotekę. - Zalecane rozwiązanie: usuń znaki
-dontoptimize,-dontshrinki-dontobfuscatez plików.flags. Kontroluj zachowanie optymalizacji wAndroid.bpza pomocą blokuoptimize(shrink,optimize,obfuscate) w przypadku docelowego elementu końcowego.
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ą dzienniki kompilacji lub próbują zapisywać pliki wyjściowe w lokalnych ścieżkach podczas kompilacji Soong w środowisku 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 w folderzeout/soong/.intermediates/.
Reguły zachowywania w wersji produkcyjnej dotyczące kodu testowego
- Dlaczego to obniża wydajność: dodanie niestandardowych reguł
-keepwyłącznie na potrzeby testowego dostępu wymusza pozostawienie tego kodu w niezaszyfrowanej postaci i zachowanie go w kompilacjach produkcyjnych. Chociaż dodawanie adnotacji do metod za pomocą symbolu@VisibleForTestinglub konfigurowanietrace_references_frompozwala uniknąć ręcznego utrzymywania reguł-keep, te symbole są nadal obecne w binarnym pliku produkcyjnym. - Zalecane rozwiązanie: aby punkty wejścia przeznaczone tylko do testowania nie były uwzględniane w pliku APK wersji produkcyjnej, podziel aplikację na
.implbibliotekę i połącz ją z samodzielnie instrumentującym się testowym plikiem APK za pomocąstatic_libs, jak opisano w kroku 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 obejmujące wszystkie elementy zachowują każdy zasób w pasującym typie w module i jego zależnościach, co niweczy zmniejszanie zasobów (
shrink_resources: true) i zwiększa rozmiar pliku APK oraz mapowanej tabeliresources.arsc. - Zalecana poprawka:
- Preferuj statyczne odwołania do zasobów zamiast
keep.xml: unikaj używania metodyResources.getIdentifierdo dynamicznego wyszukiwania ograniczonego zestawu zasobów, takich jak ponumerowane ciągi eksperymentów lub tematyczne elementy rysunkowe. Zamiast tego użyj instrukcjiswitchw czasie kompilacji lub mapowania na stałeR.id,R.stringlubR.drawable. Statyczne odwołania umożliwiają R8 i AAPT2 śledzenie dokładnych zasobów na żywo, eliminowanie narzutu związanego z wyszukiwaniem ciągów znaków w czasie działania i usuwanie potrzeby stosowaniakeep.xml. - Wymień konkretne nazwy zasobów: jeśli wymagane jest dynamiczne wyszukiwanie, np. przez czytnik licencji zewnętrznego dostawcy, wymień dokładne identyfikatory zasobów w
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 automatycznie je zachowa, więc możesz usunąćres/raw/keep.xml.
- Preferuj statyczne odwołania do zasobów zamiast
Sprawdzanie i audytowanie zmian w regułach
Za każdym razem, gdy usuwasz lub zawężasz reguły zachowywania w proguard.flags lub keep.xml, sprawdź, czy moduł jest prawidłowo kompilowany, przechodzi testy jednostkowe i testy instrumentacji oraz zachowuje wszystkie wymagane punkty wejścia.
Tworzenie i testowanie modułów
Skompiluj moduł, aby sprawdzić, czy R8 i AAPT2 działają bez ostrzeżeń o brakujących odwołaniach:
m <MODULE_NAME>Uruchom testy jednostkowe i testy z instrumentacją modułu za pomocą polecenia
atest:atest <TEST_MODULE_NAME>W przypadku aplikacji systemowych, usług uprzywilejowanych lub modułów zależnych od sprzętu uruchamiaj testy instrumentacyjne i testy interfejsu na docelowych urządzeniach fizycznych lub w laboratorium testowym, aby przetestować ścieżki odbicia 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 i po zmianach reguł, aby potwierdzić, że R8 usuwa martwy 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>.apk
Analizowanie promienia zachowywania i podporządkowania reguł za pomocą R8
Kompilator R8 o otwartym kodzie źródłowym 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 Android (skonfigurowaną w build/soong/java/dex.go).
Aby przeanalizować reguły zachowywania w przypadku 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 wartość
R8_DUMP_KEEP_RADIUS=true, Soong instruuje R8, aby rejestrował dane reguł keep w pliku pośrednimr8keepradius.pbdla każdego skompilowanego modułu w folderzeout/soong/.intermediates/. W przypadku każdej reguły zachowywania ikeepannoadnotacji R8 rejestruje:- Promień natychmiastowego zachowania: dokładne klasy, pola i metody zachowane przez regułę wraz z określonymi ograniczeniami, które wymusza w zakresie zmniejszania, optymalizacji lub zaciemniania.
- Podporządkowanie reguł: które inne reguły przechowywania lub adnotacje już zachowują te same elementy. Jeśli reguła niestandardowa jest w całości zawarta w regule AAPT2 lub globalnej regule podstawowej, możesz ją bezpiecznie usunąć.
- Reguły dotyczące całego pakietu i reguły globalne: reguły, które używają szerokich symboli wieloznacznych obejmujących cały pakiet lub stosują globalne dyrektywy konfiguracyjne.
Przekształć dane wyjściowe
r8keepradius.pbw interaktywny raport HTML za pomocą narzędziaKeepRadiusHtmlReportGenerator(dołączonego doprebuilts/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 ścieżkę do katalogu,
KeepRadiusHtmlReportGeneratorprzejdzie wszystkie*keepradius*.pbpliki, wygeneruje raport HTML dla każdego modułu i utworzyout/keep_radius_reports/keepradius.htmlpodsumowanie liczby aktywnych elementów, liczby zachowanych elementów i reguł przechowywania o największym promieniu w całej kompilacji. Więcej informacji o interpretowaniu w wygenerowanym raporcie wyników dotyczących zmniejszania, optymalizacji i zaciemniania znajdziesz w artykule Korzystanie z analizatora konfiguracji R8.
Soong obsługuje też 2 dodatkowe zmienne środowiskowe diagnostyki R8 w 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 scalane konfiguracje ProGuard na potrzeby samodzielnego odtwarzania R8.R8_DUMP_PERFETTO_TRACE=true: zapisujer8trace.ptracew katalogu pośrednim modułu, aby umożliwić sprawdzanie przebiegów kompilacji R8 w Perfetto.
Weryfikowanie reguł dotyczących użytkowników biblioteki
Gdy eksporty java_library lub android_library przekazują reguły przechowywania do odbiorców niższego szczebla (export_proguard_flags_files: true), reguły te nie mogą zawierać globalnych flag, które zmieniają lub wyłączają optymalizację w aplikacji odbierającej.
R8 udostępnia analizator reguł zachowywania open source (ProcessKeepRules), który jest dostępny w platformie Android 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 konfiguracyjny i w przypadku napotkania dyrektyw niedozwolonych w regułach dotyczących użytkowników biblioteki wyświetla diagnostykę pliku i wiersza. Niedozwolone dyrektywy obejmują optymalizację globalną, zmniejszanie lub wyłączanie zaciemniania (np. -dontoptimize lub -dontshrink), flagi ponownego pakowania i modyfikacji dostępu, flagi diagnostyczne lub mapowania oraz flagi -keepattributes na poziomie aplikacji.
Sprawdzanie drzew źródeł za pomocą skryptu pgaudit.py
Aby przeprowadzić audyt niekompilowanych katalogów źródłowych wraz z analizatorami czasu kompilacji R8, drzewo platformy Android zawiera skrypt pgaudit.py w katalogu build/make/core/proguard/tools/. To narzędzie do analizy statycznej skanuje pliki Android.bp, proguard.flags i keep.xml w repozytorium, aby oznaczać reguły, które są już objęte przez AAPT2 lub globalne platformy bazowe, szerokie symbole wieloznaczne, zastąpienia -dontoptimize i reguły zachowywania tylko na potrzeby 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 platformy i OEM mogą łączyć pgaudit.py, aby szybko triażować drzewo źródłowe, process-keep-rules, aby weryfikować wyeksportowane reguły biblioteki, oraz R8_DUMP_KEEP_RADIUS=true z KeepRadiusHtmlReportGenerator, aby mierzyć dokładny promień zachowania klasy, pola i metody każdej reguły w kompilacji.