15/06/2024
Ești un dezvoltator Android și te-ai confruntat vreodată cu un mesaj de eroare precum "trouble writing output: Too many field references: 131000; max is 65536" sau "Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536"? Dacă răspunsul este da, atunci ai ajuns la limita de metode a arhitecturii de compilare Android, o problemă comună pentru aplicațiile extinse. Acest număr, 65.536, reprezintă numărul total de referințe de metode pe care codul tău le poate invoca într-un singur fișier Dalvik Executable (DEX). Această limitare poate deveni o barieră semnificativă pe măsură ce aplicațiile cresc în complexitate și integrează tot mai multe biblioteci. Din fericire, există o soluție eficientă: Multidex. Acest articol îți va explica în detaliu ce este această limită, de ce apare și cum poți configura și optimiza aplicația ta pentru a depăși cu succes această provocare, permițându-i să compileze și să ruleze cu multiple fișiere DEX.

Înțelegerea Limitei de Referințe de 64k
Fișierele aplicațiilor Android (APK-uri) conțin fișiere de bytecode executabile în format Dalvik Executable (DEX). Aceste fișiere includ codul compilat folosit pentru a rula aplicația ta pe un dispozitiv Android. Specificația Dalvik Executable impune o limită de 65.536 de referințe de metode per fișier DEX. Această limită include nu doar metodele din propriul tău cod, ci și pe cele din framework-ul Android și din toate bibliotecile pe care le-ai inclus în proiect. Termenul "kilo" în contextul informaticii denotă 1.024 (sau 2^10). Deoarece 65.536 este egal cu 64 x 1.024, această limită este cunoscută popular sub numele de "limita de referințe de 64k". Atunci când o aplicație depășește această limită, sistemul de compilare nu mai poate crea un singur fișier DEX valid, rezultând erorile menționate anterior. Această situație devine din ce în ce mai frecventă pe măsură ce dezvoltatorii utilizează biblioteci terțe complexe, care, la rândul lor, aduc un număr considerabil de metode în aplicație.
Compatibilitatea Multidex cu Versiunile Anterioare de Android (sub 5.0)
Pentru versiunile platformei Android anterioare versiunii 5.0 (nivel API 21), codul aplicațiilor este executat de runtime-ul Dalvik. În mod implicit, Dalvik limitează aplicațiile la un singur fișier de bytecode classes.dex per APK. Pentru a depăși această limitare pe dispozitivele mai vechi, trebuie să adaugi o bibliotecă specială de suport Multidex în fișierul build.gradle sau build.gradle.kts la nivel de modul. Această bibliotecă devine parte a fișierului DEX principal al aplicației tale și gestionează apoi accesul la fișierele DEX suplimentare și la codul pe care acestea îl conțin.
Pentru a include biblioteca Multidex, adaugă următoarea dependență în fișierul tău build.gradle:
dependencies { def multidex_version = "2.0.1" implementation "androidx.multidex:multidex:$multidex_version" }Sau, dacă folosești Kotlin DSL pentru build.gradle.kts:
dependencies { val multidex_version = "2.0.1" implementation("androidx.multidex:multidex:$multidex_version") }Pe lângă adăugarea dependenței, trebuie să configurezi aplicația pentru a utiliza suportul Multidex. Există două moduri principale de a face acest lucru, în funcție de modul în care utilizezi clasa Application:
1. Dacă nu suprascrii clasa Application:
Modifică fișierul AndroidManifest.xml și setează atributul android:name în eticheta <application> la androidx.multidex.MultiDexApplication:
<?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>2. Dacă suprascrii clasa Application:
Schimbă clasa ta Application existentă pentru a extinde MultiDexApplication:
// Kotlin class MyApplication: MultiDexApplication() { ... }// Java public class MyApplication extends MultiDexApplication { ... }Dacă suprascrii clasa Application, dar nu poți schimba clasa de bază (de exemplu, dacă deja extinzi o altă clasă), poți, ca alternativă, să suprascrii metoda attachBaseContext() și să invoci MultiDex.install(this) pentru a activa 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); } }Atenție: Este crucial să nu execuți MultiDex.install() sau orice alt cod folosind reflecție sau JNI înainte ca MultiDex.install() să fie complet. Urmărirea Multidex nu va acționa asupra acestor apeluri, ceea ce poate duce la erori ClassNotFoundException sau probleme de verificare din cauza unei partiționări incorecte a clasei între fișierele DEX.
Compatibilitatea Multidex cu Android 5.0 și Versiunile Ulterioare
Începând cu Android 5.0 (nivel API 21) și versiunile ulterioare, platforma utilizează un runtime numit ART (Android Runtime). Spre deosebire de Dalvik, ART suportă nativ încărcarea de mai multe fișiere DEX din APK-uri. În timpul instalării aplicației, ART efectuează o pre-compilare, scanând fișierele classesN.dex și compilându-le într-un singur fișier OAT pentru executare pe dispozitivul Android. Acest lucru înseamnă că, dacă minSdkVersion al aplicației tale este setat la 21 sau o versiune ulterioară, Multidex este activat implicit și nu este necesar să adaugi biblioteca androidx.multidex:multidex sau să modifici clasa Application. Această abordare simplifică semnificativ procesul de configurare pentru aplicațiile moderne.
Este important de reținut că, atunci când rulezi aplicația cu Android Studio, compilarea este optimizată pentru dispozitivele țintă pe care va fi implementată. Aceasta include activarea Multidex atunci când dispozitivele țintă rulează Android 5.0 și versiuni ulterioare. Totuși, deoarece această optimizare se aplică doar atunci când implementezi aplicația cu Android Studio, s-ar putea să fie necesar să configurezi totuși compilarea pentru versiunea de lansare pentru Multidex, dacă dorești să eviți limita de 64k în mediul de producție.
Strategii pentru Evitarea Limitei de 64k (Înainte de a Activa Multidex)
Înainte de a te grăbi să configurezi aplicația pentru a utiliza Multidex, este o practică bună să încerci să reduci numărul total de referințe de metode pe care le invocă codul aplicației tale. Acest lucru include metodele definite de propriul tău cod, precum și cele din bibliotecile incluse. Reducerea dimensiunii generale a APK-ului și evitarea atingerii limitei de 64k sunt obiective valoroase în sine. Iată câteva strategii cheie:
- Revizuiește dependențele directe și tranzitive ale aplicației tale: Adesea, dezvoltatorii includ o bibliotecă foarte mare doar pentru a utiliza câteva metode de utilitate. Analizează cu atenție dacă valoarea oricărei dependențe de bibliotecă semnificative pe care o incluzi în aplicația ta justifică volumul de cod adăugat. Reducerea dependențelor de cod ale aplicației tale ajută adesea la evitarea limitei de referințe DEX. Există, de exemplu, biblioteci precum Google Play Services care pot fi incluse modular, permițându-ți să adaugi doar componentele specifice de care ai nevoie, în loc de întregul pachet.
- Elimină codul neutilizat cu R8: Activează reducerea codului pentru a rula R8 pentru compilările de lansare (release builds). R8 este un instrument de optimizare care asigură că nu trimiți cod neutilizat cu APK-urile tale. Dacă este configurat corect, R8 poate, de asemenea, să elimine codul și resursele neutilizate din dependențele tale. Acest lucru este deosebit de eficient pentru bibliotecile mari, unde o mare parte din cod ar putea să nu fie niciodată invocată de aplicația ta. Asigură-te că ai fișierul
proguard-rules.proconfigurat corect pentru a nu elimina cod esențial.
Aceste tehnici te pot ajuta să reduci dimensiunea generală a APK-ului și, în multe cazuri, să eviți complet necesitatea de a activa Multidex în aplicația ta, simplificând procesul de compilare și reducând potențialele probleme de performanță la pornire pe dispozitivele mai vechi.
Configurarea Aplicației Tale pentru Multidex (Pași Detaliați)
După ce ai încercat strategiile de reducere a codului și ai determinat că ai nevoie în continuare de Multidex, iată pașii detaliați pentru configurarea corectă a aplicației tale. Reține: dacă minSdkVersion este setat la 21 sau mai mare, Multidex este activat implicit, iar biblioteca corespunzătoare nu este necesară.
Dacă minSdkVersion este configurat la 20 sau la un nivel inferior, va trebui să utilizezi biblioteca Multidex și să efectuezi următoarele modificări în proiectul aplicației tale:
- Modifică fișierul
build.gradlela nivel de modul: Activează Multidex și adaugă biblioteca Multidex ca dependență. - Modifică clasa
Applicationa aplicației tale: În funcție de dacă suprascrii sau nu clasaApplication, urmează una dintre aceste acțiuni: - Dacă nu suprascrii clasa
Application: Modifică fișierul tăuAndroidManifest.xmlpentru a definiandroid:nameîn eticheta<application>, așa cum se arată mai jos: - Dacă suprascrii clasa
Application: Trebuie să o schimbi pentru a extindeMultiDexApplication: - Dacă suprascrii clasa
Application, dar nu este posibil să schimbi clasa de bază: Ca alternativă, suprascrie metodaattachBaseContext()și invocăMultiDex.install(this)pentru a activa Multidex în felul următor:
// build.gradle (Groovy) android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 33 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }// build.gradle.kts (Kotlin) android { defaultConfig { ... minSdk = 15 targetSdk = 33 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }<?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>// Kotlin class MyApplication: MultiDexApplication() { ... }// Java public class MyApplication extends MultiDexApplication { ... }// 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); } }Când compilezi aplicația, instrumentele de compilare Android creează un fișier DEX principal (classes.dex) și fișiere DEX de rezervă (classes2.dex, classes3.dex etc.), după cum este necesar. Sistemul de compilare împachetează apoi toate fișierele DEX în APK-ul tău. La rulare, API-urile Multidex utilizează un încărcător de clasă special pentru a căuta metodele tale în toate fișierele DEX disponibile (în loc să le caute doar în fișierul classes.dex principal). Acest mecanism permite aplicației tale să depășească bariera celor 64k metode, distribuindu-le pe mai multe fișiere DEX.
Limitări ale Bibliotecii Multidex
Deși biblioteca Multidex este o soluție esențială pentru problema limitei de metode, ea vine cu anumite limitări cunoscute pe care trebuie să le iei în considerare atunci când o integrezi în configurația de compilare a aplicației tale:
- Instalarea fișierelor DEX la pornire: Instalarea fișierelor DEX secundare în timpul pornirii aplicației pe o partiție de date a dispozitivului este un proces complex și poate genera erori de tip "Aplicație nu răspunde" (ANR - Application Not Responding), mai ales dacă fișierele DEX secundare sunt mari. Pentru a evita această problemă, este recomandat să activezi reducerea codului (code shrinking) pentru a minimiza dimensiunea fișierelor DEX și a elimina părțile de cod neutilizate. Un APK mai mic, cu fișiere DEX optimizate, va reduce semnificativ riscul de ANR-uri.
- Limita
linearallocpe versiunile mai vechi: Când rulezi aplicații cu Multidex pe versiuni anterioare Android 5.0 (nivel API 21), utilizarea Multidex ajută la evitarea limiteilinearalloc(o problemă veche, de exemplu, problema 37008143). Această limită a fost crescută în Android 4.0 (nivel API 14), dar nu a rezolvat complet problema. Pe versiunile anterioare Android 4.0, este posibil să atingi limitalinearallocînainte de a atinge limita de indice DEX. Dacă aplicația ta vizează niveluri API sub 14, este esențial să efectuezi teste amănunțite pe aceste versiuni de platformă, deoarece aplicația ta ar putea avea probleme în timpul pornirii sau la încărcarea unor grupuri specifice de clase. Reducerea codului poate diminua sau chiar elimina aceste probleme.
În general, testarea riguroasă pe o varietate de dispozitive și versiuni Android este crucială atunci când folosești Multidex, pentru a asigura o experiență de utilizator fluidă și stabilă.
Declararea Claselor Necesară în Fișierul DEX Principal (MultiDexKeepProguard)
Când instrumentele de compilare creează fiecare fișier DEX pentru o aplicație Multidex, ele iau decizii complexe pentru a determina ce clase sunt absolut necesare în fișierul DEX principal (classes.dex) pentru ca aplicația să poată porni cu succes. Dacă una dintre clasele esențiale pentru pornire nu este inclusă în fișierul DEX principal, aplicația ta se va bloca cu o eroare java.lang.NoClassDefFoundError. Această problemă apare de obicei atunci când căile de acces la cod sunt mai puțin vizibile pentru instrumentele de compilare, de exemplu, când o bibliotecă pe care o utilizezi are dependențe complexe sau când codul tău folosește introspecție (reflection) sau invocare de metode Java din cod nativ.
Pentru a remedia această situație și a te asigura că toate clasele critice sunt incluse în fișierul DEX principal, trebuie să specifici manual clasele suplimentare necesare. Acest lucru se face declarându-le cu proprietatea multiDexKeepProguard în tipul tău de compilare (build type). Dacă o clasă se potrivește cu o regulă din fișierul multiDexKeepProguard, ea va fi adăugată în fișierul DEX principal.
Proprietatea multiDexKeepProguard
Fișierul specificat în multiDexKeepProguard utilizează același format ca ProGuard și suportă întreaga sa gramatică. Poți crea un fișier, de exemplu, numit multidex-config.pro, care arată similar cu următoarele exemple:
Pentru a păstra anumite clase specifice:
-keep class com.example.MyClass -keep class com.example.MyClassTooPentru a specifica toate clasele dintr-un pachet:
-keep class com.example.** { *; } // Toate clasele din pachetul com.exampleApoi, poți declara acest fișier pentru un tip de compilare, cum ar fi release, în build.gradle:
// build.gradle (Groovy) android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }// build.gradle.kts (Kotlin) android { buildTypes { getByName("release") { multiDexKeepProguard = file("multidex-config.pro") ... } } }Utilizarea corectă a multiDexKeepProguard este esențială pentru stabilitatea aplicațiilor Multidex, în special în scenarii complexe unde dependențele sunt încărcate dinamic sau prin reflecție.
Optimizarea Multidex în Compilările de Dezvoltare
O configurație Multidex necesită un timp de procesare a compilării considerabil mai mare, deoarece sistemul de compilare trebuie să ia decizii complexe cu privire la clasele care ar trebui incluse în fișierul DEX principal și cele care pot fi incluse în fișierele DEX secundare. Din acest motiv, compilările incrementale care utilizează Multidex durează, de obicei, mai mult și pot încetini procesul tău de dezvoltare. Pentru a atenua timpii mai lungi de compilare incrementală, poți utiliza procesul de pre-dexing pentru a reutiliza rezultatele Multidex între compilări. Procesul de pre-dexing se bazează pe un format ART disponibil doar pe Android 5.0 (nivel API 21) și versiunile ulterioare. Dacă utilizezi Android Studio, IDE-ul utilizează automat procesul de pre-dexing atunci când implementezi aplicația pe un dispozitiv cu Android 5.0 (nivel API 21) sau o versiune ulterioară. Totuși, dacă execuți compilări Gradle din linia de comandă, trebuie să setezi minSdkVersion la nivelul API 21 sau la un nivel superior pentru a activa procesul de pre-dexing.
Pentru a păstra configurația compilării tale de producție (care ar putea viza un minSdkVersion mai mic) și a optimiza în același timp compilările de dezvoltare, poți crea două versiuni ale aplicației folosind variante de produse (product flavors): o variantă de dezvoltare și una de lansare, cu valori diferite pentru minSdkVersion, așa cum se arată mai jos:
// build.gradle (Groovy) android { defaultConfig { ... multiDexEnabled true // Nivelul minim implicit de API pe care vrei să-l suporti. minSdkVersion 15 } productFlavors { // Include setările pe care vrei să le păstrezi doar în timpul dezvoltării. dev { // Activează pre-dexing pentru compilările din linia de comandă. // Când utilizezi Android Studio 2.3 sau o versiune ulterioară, IDE-ul activează pre-dexing // la implementarea aplicației pe un dispozitiv care rulează Android 5.0 (API nivel 21) // sau o versiune ulterioară, indiferent de minSdkVersion. minSdkVersion 21 } prod { // Dacă ai configurat blocul defaultConfig pentru versiunea de producție a aplicației tale, // poți lăsa acest bloc gol, iar Gradle va utiliza configurațiile din blocul defaultConfig. // Trebuie să incluzi totuși această variantă. În caz contrar, toate variantele // vor utiliza configurațiile variantei "dev". } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation "androidx.multidex:multidex:2.0.1" }// build.gradle.kts (Kotlin) android { defaultConfig { ... multiDexEnabled = true // Nivelul minim implicit de API pe care vrei să-l suporti. minSdk = 15 } productFlavors { create("dev") { // Activează pre-dexing pentru compilările din linia de comandă. // Când utilizezi Android Studio 2.3 sau o versiune ulterioară, IDE-ul activează pre-dexing // la implementarea aplicației pe un dispozitiv care rulează Android 5.0 (API nivel 21) // sau o versiune ulterioară, indiferent de minSdkVersion. minSdk = 21 } create("prod") { // Dacă ai configurat blocul defaultConfig pentru versiunea de producție a aplicației tale, // poți lăsa acest bloc gol, iar Gradle va utiliza configurațiile din blocul defaultConfig. // Trebuie să incluzi totuși această variantă. În caz contrar, toate variantele // vor utiliza configurațiile variantei "dev". } } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } } dependencies { implementation("androidx.multidex:multidex:2.0.1") }Această abordare îți permite să ai timpi de compilare rapizi în timpul dezvoltării, profitând de pre-dexing pe dispozitivele moderne, menținând în același timp compatibilitatea cu versiunile mai vechi de Android pentru versiunea de lansare a aplicației tale. Poți chiar să oferi un fișier manifest diferit pentru fiecare variantă (astfel încât doar varianta pentru nivelul API 20 și nivelurile inferioare să schimbe numele etichetei <application>) sau să creezi o subclasă separată de Application pentru fiecare variantă (astfel încât doar varianta pentru nivelul API 20 și nivelurile inferioare să extindă clasa MultiDexApplication sau să apeleze MultiDex.install(this)).
Testarea Aplicațiilor Multidex
Când scrii teste de instrumentare pentru aplicații Multidex, de obicei nu este necesară nicio configurare suplimentară dacă utilizezi o instrumentare standard precum MonitoringInstrumentation sau AndroidJUnitRunner. Acestea sunt deja concepute pentru a gestiona mediile Multidex. Totuși, dacă utilizezi o altă Instrumentation personalizată, trebuie să suprascrii metoda onCreate() și să adaugi apelul la MultiDex.install(targetContext) înainte de a apela super.onCreate(). Acest lucru asigură că mediul de testare este pregătit corespunzător pentru a încărca toate fișierele DEX necesare pentru testele tale.
// Kotlin fun onCreate(arguments: Bundle) { MultiDex.install(targetContext) super.onCreate(arguments) // ... restul codului onCreate }// Java public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); // ... restul codului onCreate }Asigură-te că testele tale acoperă scenarii diverse, inclusiv pornirea aplicației și încărcarea diferitelor module care ar putea fi distribuite pe fișiere DEX secundare. Acest lucru te va ajuta să identifici orice probleme legate de încărcarea claselor sau de performanță specifice mediului Multidex.
Întrebări Frecvente despre Multidex
- Ce este eroarea de 64k metode în Android?
- Este o limitare a specificației Dalvik Executable care permite maximum 65.536 de referințe de metode într-un singur fișier DEX. Când o aplicație depășește acest număr (inclusiv metode din propriul cod și din biblioteci), apare o eroare de compilare.
- Când am nevoie de Multidex?
- Ai nevoie de Multidex dacă aplicația ta și bibliotecile sale depășesc 65.536 de metode și
minSdkVersion-ul tău este 20 sau mai mic. PentruminSdkVersion21 (Android 5.0) sau mai mare, Multidex este activat implicit de runtime-ul ART. - Cum pot reduce numărul de metode înainte de a activa Multidex?
- Poți revizui dependențele aplicației, eliminând bibliotecile mari care sunt folosite doar parțial. De asemenea, poți activa reducerea codului (code shrinking) cu R8 pentru compilările de lansare, care elimină codul și resursele neutilizate din aplicație și dependențele sale.
- De ce aplicația mea se blochează cu
java.lang.NoClassDefFoundErrordupă ce am activat Multidex? - Aceasta se întâmplă dacă o clasă esențială pentru pornirea aplicației nu este inclusă în fișierul DEX principal. Poți remedia acest lucru utilizând proprietatea
multiDexKeepProguardîn fișierulbuild.gradlepentru a specifica manual clasele care trebuie incluse în fișierul DEX principal. - Multidex încetinește timpul de compilare?
- Da, configurația Multidex poate crește semnificativ timpul de compilare, în special pentru compilările incrementale, din cauza complexității deciziilor de partiționare a claselor. Pentru a atenua acest lucru în timpul dezvoltării, poți folosi pre-dexing (pentru API 21+) sau variante de produse cu
minSdkVersionsetat la 21 pentru varianta de dezvoltare. - Care sunt limitările bibliotecii Multidex?
- Principalele limitări includ riscul de erori ANR (Application Not Responding) la pornire, mai ales cu fișiere DEX secundare mari, și potențiale probleme legate de limita
linearallocpe dispozitivele foarte vechi (sub Android 4.0). Reducerea codului și testarea amănunțită sunt esențiale pentru a minimiza aceste riscuri.
Concluzie
Depășirea limitei de 64k metode în aplicațiile Android este o provocare comună pe măsură ce acestea devin din ce în ce mai complexe. Soluția Multidex oferă un mecanism robust pentru a gestiona această limitare, permițând aplicațiilor să utilizeze mai multe fișiere DEX. Indiferent dacă dezvolți pentru versiuni mai vechi de Android (sub 5.0) sau pentru cele moderne (5.0 și peste), înțelegerea și aplicarea corectă a configurației Multidex este crucială. Prin optimizarea dependențelor, utilizarea inteligentă a instrumentelor de reducere a codului precum R8 și configurarea atentă a fișierelor de build, poți asigura o experiență de dezvoltare eficientă și o aplicație stabilă și performantă pentru utilizatorii tăi. Nu uita importanța testării riguroase pentru a te asigura că aplicația ta funcționează impecabil pe toate dispozitivele țintă.
Dacă vrei să descoperi și alte articole similare cu Depășirea Limitei de 64k Metode în Android, poți vizita categoria Fitness.
