Jeśli Twoja aplikacja ma minSdk
interfejsu API w wersji 20 lub starszej, a aplikacja i biblioteki, do których się odwołuje, przekraczają 65 536 metod, wystąpi 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
W przypadku tych błędów wyświetla się wspólny numer: 65536. Ta liczba reprezentuje łączną liczbę odwołań, które mogą być wywoływane przez kod w jednym pliku kodu bajtowego Dalvik Executable (DEX). Z tego artykułu dowiesz się, jak obejść to ograniczenie, włączając konfigurację aplikacji znaną jako multidex, która umożliwia aplikacji tworzenie i odczytywanie wielu plików DEX.
Informacje o limicie 64 tys. znaków
Pliki aplikacji na Androida (APK) zawierają wykonywalne pliki kodu bajtowego w formie plików wykonywalnych Dalvik (DEX), które zawierają skompilowany kod używany do uruchamiania aplikacji. Specyfikacja plików wykonywalnych Dalvik ogranicza łączną liczbę metod,do których można się odwoływać w jednym pliku DEX, do 65 536 – w tym metod platformy Android, metod biblioteki i metod w Twoim kodzie.
W kontekście informatyki termin kilo, czyli K, oznacza 1024 (czyli 2^10). Ponieważ 65 536 = 64 x 1024, ten limit jest nazywany _limitem referencyjnym 64 tys._.Obsługa multidexu w wersjach Androida starszych niż 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 pliku kodu bajtowego classes.dex
na plik APK. Aby obejść to ograniczenie, dodaj bibliotekę multidex do pliku build.gradle
lub build.gradle.kts
na poziomie modułu:
Groovy
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. Aby wyświetlić aktualne wersje tej biblioteki, zobacz wersje multidex.
Więcej informacji znajdziesz w sekcji dotyczącej konfigurowania aplikacji pod kątem multidexu.Obsługa multidexu w Androidzie 5.0 i nowszych wersjach
Android 5.0 (interfejs API na poziomie 21) i nowsze wersje korzystają ze środowiska wykonawczego ART, które natywnie obsługuje wczytywanie wielu plików DEX z plików APK. ART przeprowadza wstępną kompilację podczas instalacji aplikacji, skanując pliki classesN.dex
i kompilując je w jeden plik OAT, który jest wykonywany przez urządzenie z Androidem. Dlatego jeśli Twój 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 Android Runtime (ART) i Dalvik.
Uwaga: gdy uruchamiasz aplikację za pomocą Android Studio, kompilacja jest optymalizowana pod kątem urządzeń docelowych, na których ją wdrażasz. Obejmuje to włączenie multidexu, gdy urządzenia docelowe działają na Androidzie 5.0 lub nowszym. Ta optymalizacja jest stosowana tylko podczas wdrażania aplikacji za pomocą Androida Studio, więc nadal może być konieczne skonfigurowanie kompilacji wersji dla multidexu, aby uniknąć limitu 64 tys.
Unikanie limitu 64 tys. znaków
Zanim skonfigurujesz aplikację, aby umożliwić korzystanie z 64 tys. lub większej liczby odwołań do metod, podejmij działania mające na celu zmniejszenie łącznej liczby odwołań wywoływanych przez kod aplikacji, w tym metod zdefiniowanych przez kod aplikacji lub biblioteki.
Te strategie pomogą 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 biblioteki, którą uwzględniasz w aplikacji, przewyższa ilość kodu dodawanego do aplikacji. Częstym, ale problematycznym wzorcem jest uwzględnianie bardzo dużej biblioteki, ponieważ kilka metod narzędziowych było przydatnych. Zmniejszenie zależności w kodzie aplikacji może często pomóc uniknąć limitu odwołań DEX.
- Usuwanie nieużywanego kodu za pomocą narzędzia R8
- Włącz zmniejszanie kodu, aby uruchamiać R8 w kompilacjach 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 w aplikacji multidexu.
Konfigurowanie aplikacji pod kątem multidexu
Uwaga: jeśli wartośćminSdkVersion
jest ustawiona na 21 lub wyższą, multidex jest domyślnie włączony i nie potrzebujesz biblioteki multidex.
Jeśli wartość parametru minSdkVersion
wynosi 20 lub mniej, musisz użyć biblioteki multidex i wprowadzić w projekcie aplikacji te zmiany:
-
Zmodyfikuj plik
build.gradle
na poziomie modułu, aby włączyć multidex i dodać bibliotekę multidex jako zależność, jak pokazano poniżej:Groovy
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 33 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 33 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
- W zależności od tego, czy zastąpisz klasę
Application
, wykonaj jedną z tych czynności:Jeśli nie zastąpisz klasy
Application
, zmień plik manifestu, aby ustawićandroid:name
w tagu<application>
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ąpisz klasę
Application
, zmień ją tak, aby rozszerzała klasęMultiDexApplication
:Kotlin
class MyApplication : MultiDexApplication() {...}
Java
public class MyApplication extends MultiDexApplication { ... }
Jeśli zastąpisz klasę
Application
, ale nie można 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); } }
Ostrzeżenie: nie wykonuj kodu
MultiDex.install()
ani żadnego innego kodu za pomocą odbicia lub JNI, zanim nie zakończy się działanieMultiDex.install()
. Śledzenie multidex nie będzie śledzić tych wywołań, co spowoduje błędyClassNotFoundException
lub weryfikacji z powodu nieprawidłowego podziału klas między plikami DEX.
Teraz podczas kompilowania aplikacji narzędzia do kompilacji Androida tworzą główny plik DEX (classes.dex
) i w razie potrzeby pomocnicze pliki DEX (classes2.dex
, classes3.dex
itd.).
System kompilacji pakuje następnie wszystkie pliki DEX do pliku APK.
W czasie działania zamiast wyszukiwać tylko w głównym pliku classes.dex
interfejsy API multidex używają specjalnego programu ładującego klasy do wyszukiwania metod we wszystkich dostępnych plikach DEX.
Ograniczenia biblioteki multidex
Biblioteka multidex ma pewne znane ograniczenia. Podczas włączania biblioteki do konfiguracji kompilacji aplikacji pamiętaj o tych kwestiach:
- Instalacja plików DEX podczas uruchamiania na 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życie multidexu 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 w pełni.
W przypadku wersji starszych niż Android 4.0 możesz osiągnąć limit linearalloc, zanim osiągniesz limit indeksu DEX. Jeśli kierujesz reklamy na poziomy interfejsu API niższe niż 14, dokładnie przetestuj te wersje platformy, ponieważ Twoja aplikacja może 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 tworzenia każdego pliku DEX dla aplikacji z wieloma plikami DEX narzędzia do kompilacji podejmują złożone decyzje, aby określić, które klasy są potrzebne w głównym pliku DEX, tak aby aplikacja mogła się uruchomić. Jeśli w głównym pliku DEX nie ma żadnej klasy wymaganej podczas uruchamiania, aplikacja ulega awarii i wyświetla błąd java.lang.NoClassDefFoundError
.
Narzędzia do kompilacji rozpoznają ścieżki kodu, do którego dostęp jest uzyskiwany bezpośrednio z kodu aplikacji. Problem ten 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 Javy z kodu natywnego, te klasy mogą nie zostać rozpoznane jako wymagane w głównym pliku DEX.
Jeśli otrzymasz wartość java.lang.NoClassDefFoundError
, musisz ręcznie określić dodatkowe klasy wymagane w podstawowym pliku DEX, deklarując je za pomocą właściwości multiDexKeepProguard
w typie kompilacji. Jeśli klasa zostanie dopasowana w multiDexKeepProguard
, zostanie dodana do głównego pliku DEX.
Właściwość multiDexKeepProguard
Plik multiDexKeepProguard
ma taki sam format jak ProGuard i obsługuje całą gramatykę ProGuard. Więcej informacji o tym, jak dostosować elementy, które mają być zachowane w aplikacji, znajdziesz w artykule Dostosowywanie kodu, który ma być zachowany.
Plik określony w multiDexKeepProguard
powinien zawierać opcje -keep
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 będzie 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 typu kompilacji w ten sposób:
Groovy
android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }
Kotlin
android { buildTypes { getByName("release") { multiDexKeepProguard = file("multidex-config.pro") ... } } }
Optymalizacja multidexu w wersjach deweloperskich
Konfiguracja multidexu wymaga znacznie dłuższego czasu przetwarzania kompilacji, ponieważ system kompilacji musi podejmować złożone decyzje dotyczące tego, które klasy muszą być uwzględnione w głównym pliku DEX, a które mogą być uwzględnione w dodatkowych plikach DEX. Oznacza to, że przyrostowe kompilacje z użyciem multidexu zwykle trwają dłużej i mogą spowolnić proces programowania.
Aby skrócić czas kompilacji przyrostowej, użyj wstępnego indeksowania, które umożliwia ponowne wykorzystanie danych wyjściowych multidexu między kompilacjami.
Wstępne indeksowanie zależy od formatu ART dostępnego tylko na Androidzie 5.0 (poziom interfejsu API 21) i nowszych. Jeśli używasz Androida Studio, środowisko IDE automatycznie korzysta z wstępnego indeksowania DEX podczas wdrażania aplikacji na urządzeniu z Androidem 5.0 (API na poziomie 21) lub nowszym.
Jeśli jednak uruchamiasz kompilacje Gradle z wiersza poleceń, musisz ustawić wartość minSdkVersion
na 21 lub wyższą, aby włączyć wstępne indeksowanie.
minSdkVersion
, jak pokazano poniżej:
Groovy
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ą Ci zwiększyć szybkość kompilacji w Android Studio lub wierszu poleceń, znajdziesz w artykule Optymalizacja szybkości kompilacji. Więcej informacji o korzystaniu z wariantów kompilacji znajdziesz w artykule Konfigurowanie wariantów kompilacji.
Wskazówka: jeśli masz różne warianty kompilacji dla różnych potrzeb związanych z multideksem, możesz podać inny plik manifestu dla każdego wariantu, aby tylko plik dla poziomu interfejsu API 20 i niższych zmieniał nazwę tagu <application>
. Możesz też utworzyć inną podklasę Application
dla każdego wariantu, aby tylko podklasa dla interfejsu API na poziomie 20 i niższym rozszerzała klasę MultiDexApplication
lub wywoływała MultiDex.install(this)
.
Testowanie aplikacji multidex
Jeśli piszesz testy instrumentacyjne dla aplikacji z wieloma plikami DEX, nie musisz wykonywać żadnej dodatkowej konfiguracji, o ile używasz instrumentacji
MonitoringInstrumentation
lub
AndroidJUnitRunner
. Jeśli używasz innego Instrumentation
, musisz zastąpić jego 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); ... }