Jeśli Twoja aplikacja ma parametr minSdk ustawiony na poziom interfejsu API 20 lub niższy, a aplikacja i biblioteki, do których się odwołuje, przekraczają 65 536 metod, wystąpi ten błąd kompilacji, który wskazuje, że aplikacja osiągnęła limit architektury kompilacji Androida:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
Starsze wersje systemu kompilacji zgłaszają inny błąd, który wskazuje na ten sam problem:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
Te warunki błędu wyświetlają wspólną liczbę: 65536. Ta liczba oznacza łączną liczbę odwołań, które mogą być wywoływane przez kod w jednym pliku kodu bajtowego Dalvik Executable (DEX). Z tej strony dowiesz się, jak obejść to ograniczenie, włączając konfigurację aplikacji znaną jako multidex, która umożliwia kompilowanie i odczytywanie przez aplikację wielu plików DEX.
Limit 64 tys. odwołań
Pliki aplikacji na Androida (APK) zawierają wykonywalne pliki kodu bajtowego w postaci plików Dalvik Executable (DEX), które zawierają skompilowany kod używany do uruchamiania aplikacji. Specyfikacja Dalvik Executable ogranicza łączną liczbę metod,do których można się odwoływać w jednym pliku DEX, do 65 536 – w tym metod platformy Androida, metod bibliotek i metod w Twoim kodzie.
W informatyce termin kilo lub K oznacza 1024 (czyli 2^10). Ponieważ 65 536 jest równe 64 × 1024, ten limit jest nazywany _limitem 64 tys. odwołań_.Obsługa multidex przed Androidem 5.0
Wersje platformy starsze niż Android 5.0 (poziom interfejsu API 21) używają środowiska wykonawczego Dalvik do wykonywania kodu aplikacji. Domyślnie Dalvik ogranicza aplikacje do jednego
classes.dex pliku kodu bajtowego na plik APK. Aby obejść to
ograniczenie, dodaj bibliotekę multidex do pliku build.gradle lub
build.gradle.kts
na poziomie modułu:
Dynamiczny
dependencies { def multidex_version = "2.0.1" implementation "androidx.multidex:multidex:$multidex_version" }
Kotlin
dependencies { val multidex_version = "2.0.1" implementation("androidx.multidex:multidex:$multidex_version") }
Ta biblioteka staje się częścią głównego pliku DEX aplikacji, a następnie zarządza dostępem do dodatkowych plików DEX i zawartego w nich kodu. Aktualne wersje tej biblioteki znajdziesz w sekcji Wersje multidex.
Więcej informacji znajdziesz w sekcji Konfigurowanie aplikacji pod kątem multidex .Obsługa multidex w Androidzie 5.0 i nowszych wersjach
Android 5.0 (poziom interfejsu API 21) i nowsze wersje używają środowiska wykonawczego ART, które
natywnie obsługuje wczytywanie wielu plików DEX z plików APK. ART
wykonuje wstępną kompilację podczas instalacji aplikacji, skanując
classesN.dex pliki i kompilując je w jeden
plik OAT do
wykonania przez urządzenie z Androidem. Jeśli więc parametr minSdkVersion
ma wartość 21 lub wyższą, multidex jest domyślnie włączony i nie potrzebujesz biblioteki multidex.
Więcej informacji o środowisku wykonawczym Androida 5.0 znajdziesz w artykule Środowisko wykonawcze Androida (ART) i Dalvik.
Uwaga: podczas uruchamiania aplikacji w Android Studio kompilacja jest optymalizowana pod kątem urządzeń docelowych, na których jest wdrażana. Obejmuje to włączenie multidex, gdy urządzenia docelowe działają w Androidzie 5.0 lub nowszym. Ponieważ ta optymalizacja jest stosowana tylko podczas wdrażania aplikacji w Android Studio, może być konieczne skonfigurowanie kompilacji do publikacji pod kątem multidex, aby uniknąć limitu 64 tys.
Unikanie limitu 64 tys.
Zanim skonfigurujesz aplikację tak, aby umożliwiała używanie 64 tys. lub więcej odwołań do metod, wykonaj czynności które pozwolą zmniejszyć łączną liczbę odwołań wywoływanych przez kod aplikacji, w tym metod zdefiniowanych przez kod aplikacji lub dołączonych bibliotek.
Te strategie mogą pomóc Ci uniknąć osiągnięcia limitu odwołań DEX:
- Sprawdzanie bezpośrednich i przechodnich zależności aplikacji
- Zastanów się, czy wartość dużej zależności biblioteki, którą uwzględniasz w aplikacji, przeważa nad ilością kodu dodawanego do aplikacji. Częstym, ale problematycznym wzorcem jest dołączanie bardzo dużej biblioteki ponieważ kilka metod narzędziowych było przydatnych. Zmniejszenie zależności kodu aplikacji może często pomóc w uniknięciu limitu odwołań DEX.
- Usuwanie nieużywanego kodu za pomocą R8
- Włącz zmniejszanie kodu, aby uruchomić R8 w przypadku kompilacji do publikacji. Włącz zmniejszanie, aby mieć pewność, że nie wysyłasz nieużywanego kodu z plikami APK. Jeśli zmniejszanie kodu jest prawidłowo skonfigurowane, może też usuwać nieużywany kod i zasoby z zależności.
Stosowanie tych technik może pomóc w zmniejszeniu ogólnego rozmiaru pliku APK i uniknięciu konieczności używania multidex w aplikacji.
Konfigurowanie aplikacji pod kątem multidex
Uwaga: jeśli parametrminSdkVersion ma wartość 21 lub wyższą, multidex jest domyślnie włączony i nie potrzebujesz biblioteki multidex.
Jeśli parametr minSdkVersion ma wartość 20 lub niższą, musisz użyć biblioteki multidex i wprowadzić te zmiany w projekcie aplikacji:
-
Zmodyfikuj plik
build.gradlena poziomie modułu, aby włączyć multidex i dodać bibliotekę multidex jako zależność, jak pokazano tutaj:Dynamiczny
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 36 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 36 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
- W zależności od tego, czy zastępujesz
Applicationklasę, wykonaj jedną z tych czynności:Jeśli nie zastępujesz
Applicationklasy, edytuj plik manifestu, aby ustawićandroid:namew<application>tagu w ten sposób:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:name="androidx.multidex.MultiDexApplication" > ... </application> </manifest>
Jeśli zastępujesz klasę
Application, zmień ją tak, aby rozszerzała klasęMultiDexApplication:Kotlin
class MyApplication : MultiDexApplication() {...}
Java
public class MyApplication extends MultiDexApplication { ... }
Jeśli zastępujesz klasę
Applicationale nie możesz zmienić klasy bazowej, zastąp metodęattachBaseContext()i wywołajMultiDex.install(this)aby włączyć multidex:Kotlin
class MyApplication : SomeOtherApplication() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) MultiDex.install(this) } }
Java
public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
Uwaga: nie wykonuj
MultiDex.install()ani żadnego innego kodu za pomocą refleksji lub JNI, zanim nie zakończy sięMultiDex.install(). Śledzenie multidex nie będzie śledzić tych wywołań, co spowoduje błądClassNotFoundExceptionlub błędy weryfikacji z powodu nieprawidłowego podziału klas między plikami DEX.
Teraz, gdy kompilujesz aplikację, narzędzia do kompilacji Androida tworzą główny plik DEX (classes.dex) i w razie potrzeby pomocnicze pliki DEX (classes2.dex, classes3.dex itd.).
Następnie system kompilacji pakuje wszystkie pliki DEX do pliku APK.
W czasie działania, zamiast wyszukiwać tylko w głównym
classes.dex pliku, interfejsy API multidex używają specjalnego modułu wczytywania klas do wyszukiwania metod we wszystkich dostępnych plikach DEX.
Ograniczenia biblioteki multidex
Biblioteka multidex ma pewne znane ograniczenia. Gdy włączasz bibliotekę do konfiguracji kompilacji aplikacji, weź pod uwagę te kwestie:
- Instalacja plików DEX podczas uruchamiania w partycji danych urządzenia jest złożona i może powodować błędy typu Aplikacja nie odpowiada (ANR), jeśli dodatkowe pliki DEX są duże. Aby uniknąć tego problemu, włącz zmniejszanie kodu, aby zminimalizować rozmiar plików DEX i usunąć nieużywane części kodu.
- W przypadku wersji starszych niż Android 5.0 (poziom interfejsu API 21) używanie multidex nie wystarczy, aby obejść limit linearalloc (problem 37008143). Ten limit został zwiększony w
Androidzie 4.0 (poziom interfejsu API 14), ale nie rozwiązało to problemu całkowicie.
W wersjach starszych niż Android 4.0 możesz osiągnąć limit linearalloc przed osiągnięciem limitu indeksu DEX. Jeśli więc kierujesz reklamy na poziomy interfejsu API niższe niż 14, dokładnie przetestuj aplikację w tych wersjach platformy, ponieważ może ona mieć problemy podczas uruchamiania lub wczytywania określonych grup klas.
Zmniejszanie kodu może ograniczyć lub wyeliminować te problemy.
Deklarowanie klas wymaganych w głównym pliku DEX
Podczas kompilowania każdego pliku DEX dla aplikacji multidex narzędzia do kompilacji podejmują złożone decyzje, aby określić, które klasy są potrzebne w głównym pliku DEX, aby aplikacja mogła się pomyślnie uruchomić. Jeśli w głównym pliku DEX nie ma klasy wymaganej podczas uruchamiania, aplikacja ulegnie awarii z błędem java.lang.NoClassDefFoundError.
Narzędzia do kompilacji rozpoznają ścieżki kodu, do którego uzyskuje się dostęp bezpośrednio z kodu aplikacji. Ten problem może jednak wystąpić, gdy ścieżki kodu są mniej widoczne, np. gdy używana biblioteka ma złożone zależności. Jeśli na przykład kod używa introspekcji lub wywoływania metod Java z kodu natywnego, te klasy mogą nie zostać rozpoznane jako wymagane w głównym pliku DEX.
Jeśli otrzymasz java.lang.NoClassDefFoundError, musisz ręcznie określić dodatkowe klasy wymagane w głównym pliku DEX, deklarując je za pomocą właściwości multiDexKeepProguard w rodzaju kompilacji. Jeśli klasa zostanie dopasowana w
pliku multiDexKeepProguard, zostanie dodana do głównego pliku DEX.
Właściwość multiDexKeepProguard
Plik multiDexKeepProguard używa tego samego formatu co ProGuard i obsługuje
całą gramatykę ProGuard. Więcej informacji o tym, jak dostosować, co ma być zachowane w aplikacji, znajdziesz w
artykule Dostosowywanie kodu, który ma zostać zachowany.
Plik określony w multiDexKeepProguard powinien zawierać -keep
opcje w dowolnej prawidłowej składni ProGuard. Na przykład,
-keep com.example.MyClass.class. Możesz utworzyć plik o nazwie
multidex-config.pro który wygląda tak:
-keep class com.example.MyClass -keep class com.example.MyClassToo
Jeśli chcesz określić wszystkie klasy w pakiecie, plik będzie wyglądać tak:
-keep class com.example.** { *; } // All classes in the com.example package
Następnie możesz zadeklarować ten plik dla rodzaju kompilacji w ten sposób:
Dynamiczny
android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }
Kotlin
android { buildTypes { getByName("release") { multiDexKeepProguard = file("multidex-config.pro") ... } } }
Optymalizowanie multidex w kompilacjach deweloperskich
Konfiguracja multidex wymaga znacznie dłuższego czasu przetwarzania kompilacji, ponieważ system kompilacji musi podejmować złożone decyzje dotyczące tego, które klasy mają być uwzględnione w głównym pliku DEX, a które w dodatkowych plikach DEX. Oznacza to, że kompilacje przyrostowe z użyciem multidex zwykle trwają dłużej i mogą spowolnić proces tworzenia aplikacji.
Aby skrócić czas kompilacji przyrostowej, użyj
wstępnego indeksowania, aby ponownie wykorzystać dane wyjściowe multidex między kompilacjami.
Konwertowanie do plików .dex opiera się na formacie ART dostępnym tylko w Androidzie 5.0 (poziom interfejsu API 21) i nowszych wersjach. Jeśli używasz Android Studio, IDE automatycznie używa konwertowania do plików .dex podczas wdrażania aplikacji na urządzeniu z Androidem 5.0 (poziom interfejsu API 21) lub nowszym.
Jeśli jednak uruchamiasz kompilacje Gradle z wiersza poleceń, musisz ustawić parametr
minSdkVersion na 21 lub wyższy, aby włączyć konwertowanie do plików .dex.
minSdkVersion, jak pokazano poniżej:
Dynamiczny
android { defaultConfig { ... multiDexEnabled true // The default minimum API level you want to support. minSdkVersion 15 } productFlavors { // Includes settings you want to keep only while developing your app. dev { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdkVersion 21 } prod { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... multiDexEnabled = true // The default minimum API level you want to support. minSdk = 15 } productFlavors { // Includes settings you want to keep only while developing your app. create("dev") { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdk = 21 } create("prod") { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
Więcej strategii, które pomogą zwiększyć szybkość kompilacji w Android Studio lub wierszu poleceń, znajdziesz w artykule Optymalizowanie szybkości kompilacji. Więcej informacji o używaniu wariantów kompilacji znajdziesz w artykule Konfigurowanie wariantów kompilacji.
Wskazówka: jeśli masz różne warianty kompilacji na potrzeby różnych
multidex, możesz podać inny plik manifestu dla każdego
wariantu, aby tylko plik dla poziomu interfejsu API 20 i niższego zmieniał
nazwę tagu <application>. Możesz też
utworzyć inną Application podklasę dla każdego wariantu, aby
tylko podklasa dla poziomu interfejsu API 20 i niższego rozszerzała klasę MultiDexApplication lub
wywoływała MultiDex.install(this).
Testowanie aplikacji multidex
Podczas pisania testów instrumentacji dla aplikacji multidex nie jest wymagana żadna dodatkowa konfiguracja
jeśli używasz
MonitoringInstrumentation lub
AndroidJUnitRunner
instrumentacji. Jeśli używasz innej
Instrumentation,
musisz zastąpić jej metodę onCreate() tym kodem:
Kotlin
fun onCreate(arguments: Bundle) { MultiDex.install(targetContext) super.onCreate(arguments) ... }
Java
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }