เปิดใช้ multidex สำหรับแอปที่มีเมธอดกว่า 64,000 วิธี

หากแอปของคุณมี minSdk ของ API 20 หรือต่ำกว่า และแอปและไลบรารีที่อ้างอิงมีจำนวนเมธอดเกิน 65,536 รายการ คุณจะพบข้อผิดพลาดในการสร้างต่อไปนี้ ซึ่งบ่งชี้ว่าแอปของคุณมีจำนวนเมธอดถึงขีดจำกัดของสถาปัตยกรรมการสร้าง Android แล้ว

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

ระบบบิลด์เวอร์ชันเก่าจะรายงานข้อผิดพลาดที่แตกต่างกัน ซึ่งเป็นข้อบ่งชี้ถึงปัญหาเดียวกัน ดังนี้

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

เงื่อนไขข้อผิดพลาดเหล่านี้จะแสดงหมายเลขทั่วไปคือ 65536 ตัวเลขนี้ แสดงจํานวนการอ้างอิงทั้งหมดที่โค้ดเรียกใช้ได้ ภายในไฟล์ไบต์โค้ด Dalvik Executable (DEX) ไฟล์เดียว หน้านี้อธิบายวิธีข้ามข้อจำกัดนี้โดย เปิดใช้การกำหนดค่าแอปที่เรียกว่า multidex ซึ่งช่วยให้แอป สร้างและอ่านไฟล์ DEX หลายไฟล์ได้

เกี่ยวกับขีดจำกัดการอ้างอิง 64,000 รายการ

ไฟล์แอป Android (APK) มีไฟล์ bytecode ที่สั่งการได้ในรูปแบบ ของไฟล์ Dalvik Executable (DEX) ซึ่งมีโค้ดที่คอมไพล์แล้วซึ่งใช้ในการเรียกใช้แอป ข้อกำหนด Dalvik Executable จำกัดจำนวนเมธอดทั้งหมดที่ อ้างอิงได้ภายในไฟล์ DEX เดียวไว้ที่ 65,536 รายการ ซึ่งรวมถึงเมธอดเฟรมเวิร์ก Android เมธอดไลบรารี และเมธอดในโค้ดของคุณเอง

ในบริบทของวิทยาการคอมพิวเตอร์ คำว่า กิโล หรือ K หมายถึง 1024 (หรือ 2^10) เนื่องจาก 65,536 เท่ากับ 64x1024 ขีดจำกัดนี้จึงเรียกว่า _ขีดจำกัดอ้างอิง 64K_

การรองรับ Multidex ก่อน Android 5.0

แพลตฟอร์มเวอร์ชันก่อน Android 5.0 (API ระดับ 21) จะใช้รันไทม์ Dalvik ในการเรียกใช้โค้ดแอป โดยค่าเริ่มต้น Dalvik จะจำกัดแอปให้มีclasses.dexไฟล์ไบต์โค้ด 1 ไฟล์ต่อ APK หากต้องการหลีกเลี่ยงข้อจำกัดนี้ ให้เพิ่มไลบรารี multidex ลงในไฟล์ build.gradle หรือ build.gradle.kts ระดับโมดูล

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

ไลบรารีนี้จะกลายเป็นส่วนหนึ่งของไฟล์ DEX หลักของแอป จากนั้นจะ จัดการการเข้าถึงไฟล์ DEX เพิ่มเติมและโค้ดที่มีอยู่ หากต้องการดูเวอร์ชันปัจจุบันของไลบรารีนี้ โปรดดู เวอร์ชัน multidex

ดูรายละเอียดเพิ่มเติมได้ที่ส่วนเกี่ยวกับวิธี กำหนดค่าแอปสำหรับ Multidex

รองรับ Multidex สำหรับ Android 5.0 ขึ้นไป

Android 5.0 (API ระดับ 21) ขึ้นไปใช้รันไทม์ที่เรียกว่า ART ซึ่งรองรับการโหลดไฟล์ DEX หลายไฟล์จากไฟล์ APK โดยเนทีฟ ART จะทำการคอมไพล์ล่วงหน้าในเวลาที่ติดตั้งแอป โดยจะสแกนหาไฟล์ classesN.dex และคอมไพล์เป็นไฟล์ OAT ไฟล์เดียวเพื่อ ให้อุปกรณ์ Android ดำเนินการ ดังนั้น หาก minSdkVersion ของคุณเป็น 21 ขึ้นไป ระบบจะเปิดใช้ Multidex โดยค่าเริ่มต้น และคุณไม่จำเป็นต้องใช้ไลบรารี Multidex

ดูข้อมูลเพิ่มเติมเกี่ยวกับรันไทม์ของ Android 5.0 ได้ที่Android Runtime (ART) และ Dalvik

หมายเหตุ: เมื่อเรียกใช้แอปโดยใช้ Android Studio ระบบจะเพิ่มประสิทธิภาพบิลด์สำหรับอุปกรณ์เป้าหมายที่คุณติดตั้งใช้งาน ซึ่งรวมถึงการเปิดใช้ Multidex เมื่ออุปกรณ์เป้าหมายใช้ Android 5.0 ขึ้นไป เนื่องจากการเพิ่มประสิทธิภาพนี้จะมีผลเฉพาะเมื่อคุณติดตั้งใช้งานแอปโดยใช้ Android Studio คุณจึงอาจยังต้องกำหนดค่าบิลด์ที่เผยแพร่ สำหรับ Multidex เพื่อหลีกเลี่ยงขีดจำกัด 64K

หลีกเลี่ยงขีดจำกัด 64K

ก่อนที่จะกำหนดค่าแอปให้เปิดใช้การอ้างอิงเมธอด 64K ขึ้นไป ให้ทำตามขั้นตอน เพื่อลดจำนวนการอ้างอิงทั้งหมดที่โค้ดของแอปเรียกใช้ ซึ่งรวมถึงเมธอดที่กำหนดโดย โค้ดของแอปหรือไลบรารีที่รวมไว้

กลยุทธ์ต่อไปนี้จะช่วยให้คุณหลีกเลี่ยงการเกินขีดจำกัดการอ้างอิง DEX ได้

ตรวจสอบการอ้างอิงโดยตรงและการอ้างอิงแบบทรานซิทีฟของแอป
พิจารณาว่าค่าของทรัพยากร Dependency ของไลบรารีขนาดใหญ่ที่คุณรวมไว้ในแอปนั้นคุ้มค่ากับปริมาณโค้ด ที่เพิ่มลงในแอปหรือไม่ รูปแบบที่พบบ่อยแต่มีปัญหาคือการรวมไลบรารีขนาดใหญ่มาก เนื่องจากมีเมธอดสาธารณูปโภคเพียงไม่กี่รายการที่มีประโยชน์ การลดการอ้างอิงโค้ดของแอปมักจะช่วย ให้คุณหลีกเลี่ยงขีดจํากัดการอ้างอิง DEX ได้
นำโค้ดที่ไม่ได้ใช้ออกด้วย R8
เปิดใช้การลดขนาดโค้ดเพื่อเรียกใช้ R8 สำหรับบิลด์ที่เผยแพร่ เปิดใช้การลดขนาดเพื่อให้แน่ใจว่าคุณ ไม่ได้จัดส่งโค้ดที่ไม่ได้ใช้ไปกับ APK หากกำหนดค่าการลดขนาดโค้ดอย่างถูกต้อง ก็จะนำโค้ดและทรัพยากรที่ไม่ได้ใช้งานออกจากทรัพยากร Dependency ได้ด้วย

การใช้เทคนิคเหล่านี้จะช่วยลดขนาดโดยรวมของ APK และ หลีกเลี่ยงการใช้ Multidex ในแอป

กำหนดค่าแอปสำหรับ multidex

หมายเหตุ: หากตั้งค่า minSdkVersion เป็น 21 ขึ้นไป ระบบจะเปิดใช้ Multidex โดยค่าเริ่มต้น และคุณไม่จำเป็นต้องใช้ไลบรารี Multidex

หาก minSdkVersion ตั้งค่าเป็น 20 หรือต่ำกว่า คุณต้องใช้ไลบรารี Multidex และทำการแก้ไขต่อไปนี้ในโปรเจ็กต์แอป

  1. แก้ไขไฟล์ build.gradle ระดับโมดูลเพื่อ เปิดใช้ multidex และเพิ่มไลบรารี multidex เป็นทรัพยากร Dependency ดังที่แสดงที่นี่

    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")
    }
  2. หากคุณลบล้างคลาส Application ให้ดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้
    • หากไม่ได้ลบล้างคลาส Application ให้แก้ไขไฟล์ Manifest เพื่อตั้งค่า android:name ในแท็ก <application> ดังนี้

      <?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>
    • หากคุณลบล้างApplication คลาส ให้เปลี่ยนเป็นขยายMultiDexApplication ดังนี้

      Kotlin

      class MyApplication : MultiDexApplication() {...}

      Java

      public class MyApplication extends MultiDexApplication { ... }
    • หากคุณลบล้างคลาส Application แต่เปลี่ยนคลาสฐานไม่ได้ ให้ลบล้างเมธอด attachBaseContext() แทน แล้วเรียกใช้ MultiDex.install(this) เพื่อเปิดใช้ 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);
        }
      }

      ข้อควรระวัง: อย่าเรียกใช้ MultiDex.install() หรือโค้ดอื่นๆ ผ่านการสะท้อน หรือ JNI ก่อนที่ MultiDex.install() จะเสร็จสมบูรณ์ การติดตาม Multidex จะไม่ติดตามการเรียกเหล่านั้น ซึ่งจะทำให้เกิดข้อผิดพลาด ClassNotFoundException หรือข้อผิดพลาดในการยืนยัน เนื่องจากการแบ่งพาร์ติชันคลาสที่ไม่ถูกต้องระหว่างไฟล์ DEX

ตอนนี้เมื่อคุณสร้างแอป เครื่องมือสร้าง Android จะสร้างไฟล์ DEX หลัก (classes.dex) และไฟล์ DEX ที่รองรับ (classes2.dex, classes3.dex และอื่นๆ) ตามที่จำเป็น จากนั้นระบบบิลด์จะแพ็กเกจไฟล์ DEX ทั้งหมดลงใน APK

ในขณะรันไทม์ แทนที่จะค้นหาเฉพาะในไฟล์ classes.dex หลัก API ของ Multidex จะใช้ตัวโหลดคลาสพิเศษเพื่อค้นหาไฟล์ DEX ที่ใช้ได้ทั้งหมดสำหรับเมธอดของคุณ

ข้อจำกัดของไลบรารี Multidex

ไลบรารี Multidex มีข้อจำกัดบางอย่างที่ทราบ เมื่อรวมไลบรารีเข้ากับการกำหนดค่าบิลด์ของแอป ให้พิจารณาสิ่งต่อไปนี้

  • การติดตั้งไฟล์ DEX ระหว่างการเริ่มต้นระบบลงในพาร์ติชันข้อมูลของอุปกรณ์มีความซับซ้อนและ อาจทำให้เกิดข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR) หากไฟล์ DEX รองมีขนาดใหญ่ หากต้องการ หลีกเลี่ยงปัญหานี้ ให้เปิดใช้การลดขนาดโค้ดเพื่อลด ขนาดของไฟล์ DEX และนำส่วนของโค้ดที่ไม่ได้ใช้ออก
  • เมื่อเรียกใช้ในเวอร์ชันก่อน Android 5.0 (API ระดับ 21) การใช้ Multidex ไม่เพียงพอที่จะหลีกเลี่ยงขีดจำกัดของ LinearAlloc (ปัญหา 37008143) ขีดจำกัดนี้เพิ่มขึ้นใน Android 4.0 (API ระดับ 14) แต่ก็ยังไม่สามารถแก้ปัญหาได้อย่างสมบูรณ์

    ใน Android เวอร์ชันต่ำกว่า 4.0 คุณอาจถึงขีดจำกัด LinearAlloc ก่อน ถึงขีดจำกัดดัชนี DEX ดังนั้นหากคุณกำหนดเป้าหมาย API ระดับต่ำกว่า 14 ให้ทดสอบอย่างละเอียดในแพลตฟอร์มเวอร์ชันเหล่านั้น เนื่องจากแอปอาจมีปัญหาเมื่อเริ่มต้นหรือเมื่อโหลดกลุ่มคลาสที่เฉพาะเจาะจง

    การลดขนาดโค้ดช่วยลดหรือ อาจขจัดปัญหาเหล่านี้ได้

ประกาศคลาสที่จำเป็นในไฟล์ DEX หลัก

เมื่อสร้างไฟล์ DEX แต่ละไฟล์สำหรับแอปแบบ Multidex เครื่องมือบิลด์จะทำการตัดสินใจที่ซับซ้อนเพื่อพิจารณาว่าต้องใช้คลาสใดในไฟล์ DEX หลักเพื่อให้แอปเริ่มต้นได้สำเร็จ หากไม่มีคลาสที่จำเป็น ในระหว่างการเริ่มต้นในไฟล์ DEX หลัก แอปจะขัดข้อง พร้อมข้อผิดพลาด java.lang.NoClassDefFoundError

เครื่องมือบิลด์จะจดจำเส้นทางโค้ดสำหรับโค้ดที่เข้าถึงได้โดยตรงจากโค้ดของแอป อย่างไรก็ตาม ปัญหานี้อาจเกิดขึ้นเมื่อเส้นทางโค้ดมองเห็นได้ยาก เช่น เมื่อไลบรารีที่คุณใช้มีความซับซ้อนในการขึ้นต่อกัน เช่น หากโค้ดใช้การตรวจสอบหรือการเรียกใช้เมธอด Java จากโค้ดเนทีฟ ระบบอาจไม่รู้จักคลาสเหล่านั้นว่าเป็นคลาสที่จำเป็นในไฟล์ DEX หลัก

หากได้รับ java.lang.NoClassDefFoundError คุณต้องระบุคลาสเพิ่มเติมที่จำเป็นในไฟล์ DEX หลักด้วยตนเองโดยการประกาศคลาสเหล่านั้นด้วยพร็อพเพอร์ตี้ multiDexKeepProguard ในประเภทบิลด์ หากมีการจับคู่คลาสใน ไฟล์ multiDexKeepProguard ระบบจะเพิ่มคลาสนั้น ลงในไฟล์ DEX หลัก

พร็อพเพอร์ตี้ multiDexKeepProguard

ไฟล์ multiDexKeepProguard ใช้รูปแบบเดียวกับ ProGuard และรองรับไวยากรณ์ ProGuard ทั้งหมด สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีปรับแต่งสิ่งที่เก็บไว้ในแอป โปรดดูปรับแต่งโค้ดที่จะเก็บไว้

ไฟล์ที่คุณระบุใน multiDexKeepProguard ควรมีตัวเลือก -keep ในไวยากรณ์ ProGuard ที่ถูกต้อง เช่น -keep com.example.MyClass.class คุณสร้างไฟล์ชื่อ multidex-config.pro ที่มีลักษณะดังนี้ได้

-keep class com.example.MyClass
-keep class com.example.MyClassToo

หากต้องการระบุคลาสทั้งหมดในแพ็กเกจ ไฟล์จะมีลักษณะดังนี้

-keep class com.example.** { *; } // All classes in the com.example package

จากนั้นคุณจะประกาศไฟล์ดังกล่าวสำหรับประเภทบิลด์ได้ดังนี้

Groovy

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

Kotlin

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

เพิ่มประสิทธิภาพ multidex ในบิลด์การพัฒนา

การกำหนดค่า Multidex ต้องใช้เวลาในการประมวลผลบิลด์เพิ่มขึ้นอย่างมาก เนื่องจากระบบบิลด์ต้องตัดสินใจที่ซับซ้อนเกี่ยวกับคลาสที่ต้องรวมไว้ในไฟล์ DEX หลักและคลาสที่รวมไว้ในไฟล์ DEX รองได้ ซึ่งหมายความว่าการบิลด์แบบเพิ่มทีละรายการโดยใช้ Multidex มักจะ ใช้เวลานานกว่าและอาจทำให้กระบวนการพัฒนาช้าลง

หากต้องการลดเวลาในการสร้างแบบเพิ่มที่นานขึ้น ให้ใช้การดำเนินการ Dex ล่วงหน้าเพื่อนำเอาต์พุต multidex กลับมาใช้ใหม่ระหว่างการสร้าง การดำเนินการก่อน DEX อาศัยรูปแบบ ART ที่ใช้ได้เฉพาะใน Android 5.0 (API ระดับ 21) ขึ้นไป หากคุณใช้ Android Studio ทาง IDE จะใช้การดำเนินการ pre-dex โดยอัตโนมัติ เมื่อติดตั้งใช้งานแอปในอุปกรณ์ที่ใช้ Android 5.0 (API ระดับ 21) ขึ้นไป อย่างไรก็ตาม หากเรียกใช้การสร้าง Gradle จากบรรทัดคำสั่ง คุณต้องตั้งค่า minSdkVersion เป็น 21 ขึ้นไปเพื่อเปิดใช้การ Dex ก่อน

หากต้องการเก็บการตั้งค่าสำหรับบิลด์เวอร์ชันที่ใช้งานจริง คุณสามารถสร้างแอป 2 เวอร์ชันโดยใช้ Product Flavors ได้ โดยเวอร์ชันหนึ่งมี Development Flavor และอีกเวอร์ชันมี Release Flavor พร้อมค่าที่แตกต่างกันสำหรับ minSdkVersion ดังที่แสดง

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับกลยุทธ์ที่จะช่วยปรับปรุงความเร็วในการบิลด์จาก Android Studio หรือบรรทัดคำสั่งได้ที่เพิ่มประสิทธิภาพความเร็วในการบิลด์ ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ตัวแปรบิลด์ได้ที่ กำหนดค่าตัวแปรบิลด์

เคล็ดลับ: หากคุณมีตัวแปรบิลด์ที่แตกต่างกันสำหรับความต้องการ Multidex ที่แตกต่างกัน คุณสามารถระบุไฟล์ Manifest ที่แตกต่างกันสำหรับแต่ละตัวแปรได้ เพื่อให้เฉพาะไฟล์สำหรับ API ระดับ 20 และต่ำกว่าเท่านั้นที่จะเปลี่ยนชื่อแท็ก <application> นอกจากนี้ คุณยัง สร้างคลาสย่อย Application ที่แตกต่างกันสำหรับแต่ละตัวแปรได้ด้วย เพื่อให้ มีเพียงคลาสย่อยสำหรับ API ระดับ 20 และต่ำกว่าเท่านั้นที่ขยายคลาส MultiDexApplication หรือ เรียกใช้ MultiDex.install(this)

ทดสอบแอป multidex

เมื่อเขียนการทดสอบเครื่องมือสำหรับแอปแบบ Multidex คุณไม่จำเป็นต้องกำหนดค่าเพิ่มเติม หากใช้ MonitoringInstrumentation หรือ AndroidJUnitRunner เครื่องมือ หากคุณใช้ Instrumentation อื่น คุณต้องลบล้างเมธอด onCreate() ด้วยโค้ดต่อไปนี้

Kotlin

fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}

Java

public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}