เปิดใช้ 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) มีไฟล์ไบต์โค้ดสั่งการในรูปแบบของไฟล์ Dalvik Executable (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 จะจำกัดแอปให้เป็นไฟล์ไบต์โค้ด classes.dex ไฟล์เดียวต่อ 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 เพื่อหลีกเลี่ยงขีดจํากัด 64 KB

หลีกเลี่ยงขีดจํากัด 64 KB

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

กลยุทธ์ต่อไปนี้จะช่วยหลีกเลี่ยงไม่ให้เกินขีดจำกัดข้อมูลอ้างอิงของ 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 class ให้เปลี่ยนเป็น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

ขณะรันไทม์ MultiDex API จะใช้ตัวโหลดคลาสพิเศษเพื่อค้นหาเมธอดของคุณในไฟล์ DEX ทั้งหมดที่มีอยู่แทนที่จะค้นหาเฉพาะในไฟล์ classes.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

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

หากต้องการลดเวลาในการสร้างที่เพิ่มขึ้น ให้ใช้การแยกไฟล์ Dex ล่วงหน้าเพื่อนำเอาเอาต์พุต MultiDex มาใช้ซ้ำระหว่างการสร้าง การแปลงไฟล์ก่อนการจัดทําไฟล์ DEX จะใช้รูปแบบ 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);
  ...
}