เปิดใช้ 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) มีไฟล์ไบต์โค้ดปฏิบัติการในรูปแบบ ของดัลวิก ไฟล์ปฏิบัติการ (DEX) ซึ่งมีโค้ดที่คอมไพล์แล้วซึ่งใช้เพื่อเรียกใช้แอป ข้อกำหนดเฉพาะของ Dalvik Executable จะจำกัดจำนวนเมธอดทั้งหมดที่ อ้างอิงได้ภายในไฟล์ DEX ไฟล์เดียวถึง 65,536 ไฟล์ ซึ่งรวมถึง Android ด้วย เมธอดของเฟรมเวิร์ก เมธอดของไลบรารี และเมธอดในโค้ดของคุณเอง

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

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

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

ดึงดูด

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 ตั้งแต่แรก ศิลปะ ทำการคอมไพล์ล่วงหน้า ณ เวลาที่ติดตั้งแอป โดยสแกนหา classesN.dex ไฟล์และรวมเป็น 1 ไฟล์ ไฟล์ 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 แบบโดยตรงและแบบทรานซิทีฟของแอป
พิจารณาว่ามูลค่าของทรัพยากร Dependency ของไลบรารีขนาดใหญ่ที่คุณใส่ในแอปมีน้ำหนักมากกว่าโค้ดหรือไม่ กำลังเพิ่มลงในแอป รูปแบบที่พบบ่อยแต่เป็นปัญหาคือการรวมไลบรารีขนาดใหญ่มาก เพราะวิธีการยูทิลิตีที่มีประโยชน์ไม่กี่วิธี การลดทรัพยากร Dependency ของโค้ดแอปมักจะช่วยแก้ปัญหานี้ได้ เพื่อหลีกเลี่ยงขีดจำกัดการอ้างอิง DEX
นำโค้ดที่ไม่ได้ใช้ออกด้วย R8
เปิดใช้การย่อโค้ดเพื่อเรียกใช้ R8 สำหรับบิลด์ที่เผยแพร่ เปิดใช้การย่อขนาดลงเพื่อช่วยให้มั่นใจได้ว่า จะไม่จัดส่งโค้ดที่ไม่ได้ใช้งานด้วย APK ของคุณ หากมีการกำหนดค่าการย่อโค้ดอย่างถูกต้อง สามารถนำโค้ดและทรัพยากรที่ไม่ได้ใช้ออกจากทรัพยากร Dependency ได้ด้วย

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

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

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

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

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

    ดึงดูด

    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 ไม่เพียงพอที่จะหลีกเลี่ยงค่า Linear Alloc (ปัญหา 37008143) ขีดจำกัดนี้เพิ่มขึ้นในเดือน Android 4.0 (API ระดับ 14) แต่ยังแก้ปัญหาได้ไม่สมบูรณ์

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

    การย่อโค้ดสามารถลดหรือ อาจกำจัดปัญหาเหล่านี้ได้

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

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

เครื่องมือสร้างจะจดจำเส้นทางโค้ดสำหรับโค้ดที่เข้าถึงจากแอปของคุณโดยตรง โค้ด อย่างไรก็ตาม ปัญหานี้อาจ เกิดขึ้นเมื่อเส้นทางโค้ดปรากฏน้อยลง เช่น เมื่อไลบรารีที่คุณใช้มี ทรัพยากร Dependency ที่ซับซ้อน ตัวอย่างเช่น หากโค้ดใช้การพิจารณาภายในหรือการเรียกใช้ ของเมธอด 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

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

ดึงดูด

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

Kotlin

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

เพิ่มประสิทธิภาพ Multidex ในรุ่นการพัฒนา

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

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

เพื่อรักษาการตั้งค่าสำหรับ เวอร์ชันที่ใช้งานจริง คุณสามารถสร้างแอปได้ 2 เวอร์ชัน โดยใช้รสชาติของผลิตภัณฑ์ - เวอร์ชันเดียว เวอร์ชันที่กำลังพัฒนาและเวอร์ชันหนึ่งที่มีเวอร์ชันเปิดตัว ค่าที่แตกต่างกันสำหรับ minSdkVersion ดังต่อไปนี้

ดึงดูด

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);
  ...
}