Włącz multidex w przypadku aplikacji z ponad 64 tys. metod

Jeśli Twoja aplikacja ma interfejs minSdk API o wartości 20 lub starszej, a aplikacja oraz do ponad 65 536 metod,występuje ten błąd kompilacji, 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, co wskazuje na ten sam problem:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

W przypadku tych warunków błędu wyświetlana jest wspólna liczba: 65536. Ta liczba jest równa łącznej liczbie odwołań, które mogą być wywoływane przez kod w pojedynczym pliku bajtowym Dalvik Executable (DEX). Z tej strony dowiesz się, jak obejść to ograniczenie, włączając konfigurację aplikacji zwaną multidex, która umożliwia aplikacji tworzenie i odczytywanie wielu plików DEX.

Informacje o limicie 64 tys. plików referencyjnych

Pliki aplikacji na Androida (APK) zawierają wykonywalne pliki z kodem bajtowym w formacie z Dalvik Pliki wykonywalne (DEX) zawierające skompilowany kod służący do uruchamiania aplikacji. Specyfikacja pliku wykonywalnego Dalvik ogranicza łączną liczbę metod, które są mogą być odwoływane w pojedynczym pliku DEX do 65 536,w tym na urządzeniach z Androidem metod platformy, metod biblioteki i metod w Twoim kodzie.

W kontekście informatyki termin kilo lub K oznacza 1024 (lub 2^10). Ponieważ 65 536 = 64 x 1024, ten limit jest nazywany _limitem odniesienia 64 K_.

Obsługa Multidex przed Androidem 5.0

Wersje platformy starsze niż Android 5.0 (poziom interfejsu API 21) używają interfejsu Dalvik. w środowisku wykonawczym do wykonywania kodu aplikacji. Domyślnie Dalvik ogranicza aplikacje do jednej classes.dex plik z kodem bajtowym na pakiet APK. Aby obejść ten problem, dodaj bibliotekę Multidex do build.gradle na poziomie modułu lub build.gradle.kts plik:

GroovyKotlin
dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}
dependencies {
    val multidex_version = "2.0.1"
    implementation("androidx.multidex:multidex:$multidex_version")
}

Biblioteka ta staje się częścią głównego pliku DEX aplikacji, a potem zarządza dostępem do dodatkowych plików DEX i zawieranych w nich kodów. Aby wyświetlić bieżące wersje tej biblioteki, zobacz w wersji multidex.

Więcej informacji znajdziesz w sekcji dotyczącej skonfigurować aplikację pod kątem obsługi środowiska Multidex.

Obsługa Multidex na Androidzie 5.0 i nowszych

Android 5.0 (poziom interfejsu API 21) i nowsze używają środowiska wykonawczego ART, które natywnie obsługuje wczytywanie wielu plików DEX z plików APK. ART przeprowadza wstępną kompilację w momencie instalowania aplikacji, skanując pliki classesN.dex i kompilując je w jeden plik OAT, który będzie wykonywany przez urządzenie z Androidem. Dlatego, jeśli minSdkVersion ma wartość 21 lub wyższą, format Multidex jest włączony domyślnie i nie potrzebujesz biblioteki Multidex.

Więcej informacji o Androidzie 5.0 przeczytaj artykuły Środowisko wykonawcze Android (ART) i Dalvik.

Uwaga: jeśli używasz Androida Studio, kompilacja jest zoptymalizowana pod kątem urządzeń docelowych, na których wdrażasz wdrożenie. Obejmuje to włączenie Multidex, gdy uruchomione są urządzenia docelowe Androida 5.0 lub nowszego, Ta optymalizacja jest stosowana tylko podczas wdrażania aplikacji za pomocą Android Studio, dlatego nadal może być konieczne skonfigurowanie wersji produkcyjnej pod kątem multidexu, aby uniknąć limitu 64 KB.

Unikaj limitu 64K

Zanim skonfigurujesz aplikację, aby umożliwić korzystanie z 64 tys. lub więcej odwołań do metod, wykonaj czynności mające na celu zmniejszenie łącznej liczby odwołań wywoływanych przez kod aplikacji, w tym metod zdefiniowanych przez kod aplikacji lub załączone biblioteki.

Aby uniknąć przekroczenia limitu odwołań DEX, możesz zastosować te strategie:

Sprawdzanie bezpośrednich i pośrednich zależności aplikacji
Zastanów się, czy wartość dużej biblioteki, którą uwzględniasz w aplikacji, przeważa nad ilością kodu dodanego do aplikacji. Częstym, ale problematycznym rozwiązaniem jest uwzględnienie bardzo dużej biblioteki, ponieważ kilka metod pomocniczych było przydatnych. Zmniejszenie liczby zależności kodu aplikacji często pomaga uniknąć przekroczenia limitu odwołań DEX.
Usuwanie nieużywanego kodu za pomocą R8
Włącz zmniejszanie kodu, by uruchomić R8 dla kompilacji wersji. Włącz zmniejszanie, aby zapewnić nie są wysyłane nieużywany kod wraz z plikami APK. Jeśli zmniejszanie kodu jest prawidłowo skonfigurowane, może też usuwać nieużywany kod i zasoby z zależności.

Dzięki tym technikom możesz zmniejszyć ogólny rozmiar pliku APK i uniknąć stosowania w aplikacji multideksów.

Konfigurowanie aplikacji pod kątem obsługi środowiska Multidex

Uwaga: jeśli minSdkVersion ma wartość 21 lub większą, tryb multidex jest domyślnie włączony i nie potrzebujesz biblioteki Multidex.

Jeśli wartość minSdkVersion jest równa 20 lub mniejsza, musisz użyć biblioteki multidex i wprowadzić w projekcie aplikacji te zmiany:

  1. Zmodyfikuj plik build.gradle na poziomie modułu, aby włączyć MultiDex i dodać bibliotekę MultiDex jako zależność, jak pokazano tutaj:

    GroovyKotlin
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
  2. W zależności od tego, czy zastępujesz Application , wykonaj jedną z tych czynności:
    • Jeśli nie chcesz zastąpić klasy Application, otwórz plik manifestu i zmień wartość android:name w tagu <application> w następujący 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 parametr Application klasy, zmień ją na rozszerzenie MultiDexApplication w ten sposób:

      KotlinJava
      class MyApplication : MultiDexApplication() {...}
      public class MyApplication extends MultiDexApplication { ... }
    • Jeśli zastąpisz klasę Application, ale nie możesz zmienić klasy bazowej, zastąp zamiast tego metodę attachBaseContext() i wywołaj funkcję MultiDex.install(this), aby włączyć multidex:

      KotlinJava
      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }
      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(base);
           MultiDex.install(this);
        }
      }

      Uwaga: zanim wykonasz MultiDex.install() lub jakikolwiek inny kod za pomocą mechanizmu odbicia lub JNI, musi zostać ukończona funkcja MultiDex.install(). Śledzenie Multidex nie będzie śledzić tych wywołań, co spowoduje wystąpienie błędów ClassNotFoundException lub weryfikacji z powodu nieprawidłowego podziału klas między plikami DEX.

Teraz podczas tworzenia aplikacji narzędzia Android Build tworzą podstawowy plik DEX (classes.dex) i obsługujące pliki DEX (classes2.dex, classes3.dex itd.) w razie potrzeby. Następnie system kompilacji spakuje wszystkie pliki DEX do Twojego pliku APK.

W czasie działania zamiast wyszukiwania tylko w głównej aplikacji classes.dex, interfejsy API Multidex używają specjalnego programu ładującego klas do przeszukiwania wszystkich dostępne pliki DEX.

Ograniczenia biblioteki Multidex

Biblioteka Multidex ma pewne znane ograniczenia. Włączając tę bibliotekę do konfiguracji kompilacji aplikacji, weź pod uwagę te kwestie:

  • Instalacja plików DEX podczas uruchamiania na partycji danych urządzenia jest skomplikowana i może powodować błędy Aplikacja nie odpowiada (ANR), jeśli dodatkowe pliki DEX są duże. Do uniknąć tego problemu, włącz zmniejszanie kodu, by zminimalizować pliki DEX i usunąć nieużywane fragmenty kodu.
  • W wersjach starszych niż Android 5.0 (poziom interfejsu API 21) użycie multidexu nie wystarczy do obejścia limitu linearalloc (problem 37008143). Ten limit został zwiększony w Androidzie 4.0 (poziom interfejsu API 14), ale nie rozwiązał całkowicie problemu.

    W wersjach starszych niż 4.0 limit przydziału linearnego może zostać osiągnięty przed osiągnęliśmy limit indeksu DEX. Jeśli więc kierujesz reklamy na poziomy interfejsu API niższe niż 14, dokładnie przetestuj ją na tych wersjach platformy, ponieważ aplikacja może mają problemy podczas uruchamiania lub gdy są wczytywane określone grupy klas.

    Zmniejszanie kodu może zmniejszyć wyeliminować te problemy.

Zadeklaruj klasy wymagane w głównym pliku DEX.

Podczas tworzenia każdego pliku DEX dla aplikacji Multidex narzędzia do kompilacji złożonego procesu decyzyjnego w celu określenia, które klasy są potrzebne w głównej bibliotece DEX; , aby aplikacja mogła się uruchomić. Jakiekolwiek wymagane zajęcia nie jest dostarczany w głównym pliku DEX, a w rezultacie aplikacja ulega awarii. z błędem java.lang.NoClassDefFoundError.

Narzędzia do kompilacji rozpoznają ścieżki kodu, do których dostęp uzyskuje się bezpośrednio z kodu aplikacji. Ten problem może jednak występują, gdy ścieżki kodu są mniej widoczne, np. gdy używana biblioteka złożonych zależności. Jeśli na przykład kod używa introspekcji lub wywołania metod Java z kodu natywnego, te klasy mogą nie zostać uznane za wymagane w głównym pliku DEX.

Jeśli otrzymasz błąd java.lang.NoClassDefFoundError, musisz ręcznie określić dodatkowe klasy wymagane w pliku DEX, deklarując je za pomocą właściwości multiDexKeepProguard w typie 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 i całej gramatyki ProGuard. Więcej informacji o tym, jak dostosować informacje przechowywane w aplikacji, znajdziesz w artykule Wybierz kod, który chcesz zachować.

Plik określony w polu multiDexKeepProguard powinien zawierać -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 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 danego typu kompilacji w ten sposób:

android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}
android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

Optymalizuj multidex w konstrukcjach 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 należy uwzględnić w pliku DEX głównym, a które w plikach DEX pomocniczych. Oznacza to, że kompilacje przyrostowe korzystające z multidex zwykle są może potrwać dłużej i może spowolnić proces programowania.

Aby skrócić czas tworzenia kolejnych wersji, użyj pre-dexingu, aby ponownie wykorzystać dane wyjściowe multidexu między wersjami. Wyodrębnianie treści przed deksowaniem wymaga formatu ART dostępnego tylko na Androidzie 5.0 (poziom interfejsu API 21) i wyższy. Jeśli używasz Android Studio, IDE automatycznie stosuje predeksowanie podczas wdrażania aplikacji na urządzenie z Androidem 5.0 (poziom interfejsu API 21) lub nowszym. Jeśli jednak uruchamiasz kompilacje Gradle z wiersza poleceń, musisz ustawić wartość parametru minSdkVersion na 21 lub wyższą, aby włączyć predeksowanie.

Aby zachować ustawienia wersji produkcyjnej, możesz utworzyć 2 wersje aplikacji za pomocą wersji produktu – jedną wersję z wersją rozwojową i jedną wersję z wersją produkcyjną – z różnymi wartościami dla minSdkVersion, jak pokazano poniżej:

GroovyKotlin
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"
}
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")
}

Aby dowiedzieć się więcej o strategiach zwiększania szybkości kompilacji w Android Studio lub w wierszu poleceń, przeczytaj artykuł Optymalizacja szybkości kompilacji. Więcej informacji o używaniu wariantów kompilacji znajdziesz tutaj: Skonfiguruj warianty kompilacji.

Wskazówka: jeśli masz różne wersje kompilacji na potrzeby różnych potrzeb multidexu, możesz podać inny plik manifestu dla każdej wersji, aby tylko plik dla poziomu interfejsu API 20 i niżej zmieniał nazwę tagu <application>. Możesz też utwórz inną podklasę Application dla każdego wariantu, aby tylko podklasa dla interfejsu API poziomu 20 i niższych rozszerza klasę MultiDexApplication lub Wywołuje połączenie MultiDex.install(this).

Testowanie aplikacji multidex

Podczas pisania testów pomiarowych aplikacji wieloplatformowych nie trzeba nic konfigurować, jeśli używasz MonitoringInstrumentation lub AndroidJUnitRunner pomiarów. Jeśli używasz innego Instrumentation, musisz zastąpić jej metodę onCreate() tym kodem:

KotlinJava
fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}
public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}