เปิดใช้ 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 KB

ไฟล์แอป 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 ระดับโมดูล ดังนี้

GroovyKotlin
dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}
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

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

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

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

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

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

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

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

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

    ดึงดูดKotlin
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    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 ดังนี้

      KotlinJava
      class MyApplication : MultiDexApplication() {...}
      public class MyApplication extends MultiDexApplication { ... }
    • หากคุณลบล้าง Application แต่ไม่สามารถเปลี่ยนคลาสพื้นฐาน ลบล้างเมธอด attachBaseContext() แทนและเรียกใช้ MultiDex.install(this) เพื่อเปิดใช้ Multidex:

      KotlinJava
      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }
      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

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

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

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

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

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

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

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

ดึงดูดKotlin
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"
}
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() โดยใช้โค้ดต่อไปนี้

KotlinJava
fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}
public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}