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

Jeśli Twoja aplikacja ma interfejs minSdk API w wersji 20 lub starszej, a aplikacja i biblioteki, do których się ona odwołuje, przekraczają 65 536 metod, wystąpi ten błąd kompilacji. Oznacza on, ż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 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. Reprezentuje ona łączną liczbę odwołań, które mogą być wywoływane przez kod w jednym pliku bajtowym Dalvik Executable (DEX). Na tej stronie wyjaśniamy, jak obejść to ograniczenie przez włączenie konfiguracji aplikacji znanej jako 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 bajtowe w postaci plików Dalvik Executable (DEX), które zawierają skompilowany kod służący do uruchamiania aplikacji. Specyfikacja wykonywalna Dalvik ogranicza łączną liczbę metod,do których można się odwoływać w jednym pliku DEX, do 65 536 – w tym metody platformy Androida, własne metody biblioteczne i metody.

W kontekście informatyki termin kilo lub K oznacza 1024 (czyli 2^10). Liczba 65 536 jest równa 64 x 1024, więc ten limit jest nazywany limitem _64 KB_.

Obsługa Multidex w wersjach starszych niż Android 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 z kodem bajtowym classes.dex na plik APK. Aby obejść to ograniczenie, dodaj bibliotekę Multidex do pliku build.gradle lub build.gradle.kts na poziomie modułu:

Odlotowe

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 i zarządza dostępem do dodatkowych plików DEX oraz zawartych w nich kodu. Bieżące wersje tej biblioteki znajdziesz w artykule o wersjach multidex.

Więcej informacji znajdziesz w sekcji dotyczącej konfigurowania aplikacji pod kątem multidex.

Obsługa Multidex na Androidzie 5.0 i nowszych

Android 5.0 (poziom interfejsu API 21) lub nowszy korzysta ze środowiska wykonawczego o nazwie ART, które natywnie obsługuje wczytywanie wielu plików DEX z plików APK. ART przeprowadza wstępną kompilację podczas instalowania aplikacji, skanuje pliki classesN.dex i kompiluje je do pojedynczego pliku OAT w celu wykonania przez urządzenie z Androidem. Dlatego, jeśli minSdkVersion ma wartość 21 lub wyższą, format Multidex jest domyślnie włączony i nie potrzebujesz biblioteki Multidex.

Więcej informacji o środowisku wykonawczym Androida 5.0 znajdziesz w artykułach Android Runtime (ART) i Dalvik.

Uwaga: jeśli uruchamiasz aplikację w Android Studio, kompilacja jest optymalizowana pod kątem urządzeń docelowych, na których wdrażasz aplikację. Obejmuje to włączenie Multidex, gdy na urządzeniach docelowych jest zainstalowany Android 5.0 lub nowszy. Ta optymalizacja jest stosowana tylko podczas wdrażania aplikacji z użyciem Android Studio, więc nadal może być konieczne skonfigurowanie kompilacji wersji na potrzeby platformy Multidex, aby uniknąć limitu 64 KB.

Unikaj limitu 64 tys.

Zanim skonfigurujesz w aplikacji możliwość korzystania z co najmniej 64 tys. odwołań do metod, wykonaj czynności, które zmniejszą łączną liczbę odwołań wywoływanych przez kod aplikacji, w tym metody zdefiniowane w kodzie aplikacji lub dołączonych bibliotekach.

Poniższe strategie pomogą Ci uniknąć osiągnięcia limitu plików referencyjnych DEX:

Sprawdzanie zależności bezpośrednich i przechodnich aplikacji
Zastanów się, czy wartość dowolnej dużej zależności biblioteki uwzględniona w aplikacji przewyższa ilość kodu dodawanego do aplikacji. Częstym, ale problematycznym wzorcem jest uwzględnienie bardzo dużej biblioteki, ponieważ przydało się kilka metod użytkowych. Zmniejszenie zależności kodu aplikacji często pozwala uniknąć limitu plików referencyjnych DEX.
Usuń nieużywany kod za pomocą R8
Włącz zmniejszanie kodu, aby uruchamiać R8 w kompilacjach wersji. Włącz zmniejszanie, aby mieć pewność, że wraz z plikami APK nie wysyłasz nieużywanego kodu. Jeśli zmniejszanie kodu jest skonfigurowane prawidłowo, może też usunąć z zależności nieużywany kod i zasoby.

Korzystając z tych technik, możesz zmniejszyć ogólny rozmiar pliku APK i uniknąć konieczności korzystania z interfejsu Multidex w aplikacji.

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

Uwaga: jeśli minSdkVersion ma wartość 21 lub wyższą, format Multidex jest domyślnie włączony i nie potrzebujesz biblioteki Multidex.

Jeśli minSdkVersion ma wartość 20 lub niższą, musisz użyć biblioteki multidex i wprowadzić w projekcie aplikacji te zmiany:

  1. Zmodyfikuj plik build.gradle na poziomie modułu, aby włączyć interfejs Multidex, i dodaj bibliotekę multidex jako zależność w ten sposób:

    Odlotowe

    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")
    }
    
  2. W zależności od tego, czy zastępujesz klasę Application, wykonaj jedną z tych czynności:
    • Jeśli nie zastąpisz klasy Application, zmodyfikuj plik manifestu, aby w tagu <application> ustaw android:name 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ą, aby rozszerzyć zakres MultiDexApplication w ten sposób:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Jeśli zastąpisz klasę Application, ale nie będzie można zmienić klasy bazowej, zastąp metodę attachBaseContext() i wywołaj MultiDex.install(this), aby włączyć tryb 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 uruchamiaj kodu MultiDex.install() ani żadnego innego kodu za pomocą refleksji ani JNI przed zakończeniem działania MultiDex.install(). Śledzenie Multidex nie będzie śledzić tych wywołań, co spowoduje błędy ClassNotFoundException lub zweryfikowanie błędów z powodu nieprawidłowej partycji klas między plikami DEX.

Teraz podczas tworzenia aplikacji narzędzia Android Build tworzą główny plik DEX (classes.dex) i wspierające pliki DEX (classes2.dex, classes3.dex itd.) stosownie do potrzeb. Następnie system kompilacji spakuje wszystkie pliki DEX do Twojego pliku APK.

W czasie działania interfejsów API multidex interfejsy API multidex nie przeszukują tylko głównego pliku classes.dex, ale korzystają ze specjalnego modułu ładującego klas do wyszukiwania wszystkich dostępnych plików DEX pod kątem Twoich metod.

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 złożona i może powodować błędy ANR (Application Not Responding) w przypadku dużych plików DEX. Aby uniknąć tego problemu, włącz zmniejszanie kodu, by zminimalizować rozmiar plików DEX, i usuń nieużywane fragmenty kodu.
  • Jeśli korzystasz z wersji starszej niż Android 5.0 (poziom interfejsu API 21), korzystanie z multidex nie wystarczy, aby obejść limit przydziału liniowego (problem 37008143). Ten limit został zwiększony w Androidzie 4.0 (poziom interfejsu API 14), ale nie rozwiązało to całkowicie problemu.

    W wersjach starszych niż Android 4.0 limit przydziału linearnego może zostać osiągnięty przed osiągnięciem limitu indeksu DEX. Jeśli więc kierujesz aplikację na poziomy API niższe niż 14, przeprowadź dokładne testy na tych wersjach platformy, ponieważ aplikacja może mieć problemy podczas uruchamiania lub gdy są ładowane określone grupy klas.

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

Deklarowanie klas wymaganych w głównym pliku DEX

Podczas tworzenia 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 zostanie podana żadna klasa wymagana podczas uruchamiania, aplikacja ulegnie awarii i wystąpi błąd 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ą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 korzysta z introspekcji lub wywoływania metod Java z kodu natywnego, klasy te mogą nie zostać rozpoznane jako wymagane w podstawowym pliku DEX.

Jeśli otrzymasz java.lang.NoClassDefFoundError, musisz ręcznie określić dodatkowe klasy wymagane w podstawowym pliku DEX, zadeklarując je za pomocą właściwości multiDexKeepProguard w typie kompilacji. Jeśli klasa jest zgodna z plikiem multiDexKeepProguard, zostanie ona dodana do podstawowego pliku DEX.

właściwość multiDexKeepProguard

Plik multiDexKeepProguard korzysta z tego samego formatu co ProGuard i obsługuje całą gramatykę ProGuard. Więcej informacji o dostosowywaniu zawartości aplikacji znajdziesz w sekcji Wybór kodu, który ma zostać 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 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:

Odlotowe

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

Kotlin

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 muszą być uwzględnione w podstawowym pliku DEX i które klasy mogą być uwzględnione w dodatkowych plikach DEX. Oznacza to, że kompilacje przyrostowe korzystające z multidex zazwyczaj trwają dłużej i mogą spowolnić proces programowania.

Aby ograniczyć dłuższy czas kompilacji, użyj funkcji predexing w celu ponownego używania danych wyjściowych w formacie multidex między kompilacjami. Proces wstępnego deksowania korzysta z formatu ART dostępnego tylko na Androidzie 5.0 (poziom interfejsu API 21) i nowszych. Jeśli korzystasz z Android Studio, IDE automatycznie korzysta z procesu wstępnego deksowania podczas wdrażania aplikacji na urządzeniu z Androidem 5.0 (poziom interfejsu API 21) lub nowszym. Jeśli jednak uruchamiasz kompilacje Gradle z poziomu wiersza poleceń, musisz ustawić minSdkVersion na wartość 21 lub większą, aby włączyć wstępne deksowanie.

Aby zachować ustawienia kompilacji produkcyjnej, możesz utworzyć 2 wersje aplikacji z wykorzystaniem rodzajów usług – jedną z wersją deweloperską i drugą z rodzajami wersji – z różnymi wartościami atrybutu minSdkVersion, jak pokazano poniżej:

Odlotowe

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 pomagają zwiększyć szybkość kompilacji w Android Studio lub w wierszu poleceń, znajdziesz w artykule Optymalizowanie szybkości kompilacji. Więcej informacji o używaniu wariantów kompilacji znajdziesz w artykule o konfigurowaniu wariantów kompilacji.

Wskazówka: jeśli masz różne warianty kompilacji do różnych potrzeb w zakresie multidex, możesz dla każdego wariantu dostarczyć oddzielny plik manifestu, aby tylko plik dla interfejsu API na poziomie 20 i niższym 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 rozszerzyła klasę MultiDexApplication lub wywołał MultiDex.install(this).

Testowanie aplikacji multidex

Jeśli używasz instrumentacji MonitoringInstrumentation lub AndroidJUnitRunner, jeśli piszesz testy instrumentacji dla aplikacji multidex, nie musisz niczego konfigurować. Jeśli używasz innej metody 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);
  ...
}