หากแอปของคุณมี 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 หลายไฟล์ได้
เกี่ยวกับขีดจำกัดการอ้างอิง 64K
ไฟล์แอป 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 ระดับโมดูล ดังนี้
ดึงดูด
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
ก่อนกำหนดค่าแอปเพื่อเปิดใช้การอ้างอิงเมธอด 64,000 รายการขึ้นไป ให้ทำตามขั้นตอนเพื่อลดจำนวนการอ้างอิงทั้งหมดที่โค้ดของแอปเรียกใช้ ซึ่งรวมถึงเมธอดที่กำหนดโดยโค้ดของแอปหรือไลบรารีที่รวมไว้
กลยุทธ์ต่อไปนี้จะช่วยให้คุณหลีกเลี่ยงการอ้างอิง DEX เกินขีดจำกัดได้
- ตรวจสอบทรัพยากร Dependency โดยตรงและแบบทรานซิทีฟของแอป
- พิจารณาว่าค่าของทรัพยากร Dependency ของไลบรารีขนาดใหญ่ที่คุณรวมไว้ในแอปมีน้ำหนักมากกว่าจำนวนโค้ดที่เพิ่มลงในแอปหรือไม่ รูปแบบที่พบได้ทั่วไปแต่เป็นปัญหาคือการรวมไลบรารีขนาดใหญ่มากเนื่องจากมีเมธอดของยูทิลิตีบางอย่างที่เป็นประโยชน์ การลดทรัพยากร Dependency ของโค้ดแอปมักจะช่วยให้คุณหลีกเลี่ยงขีดจำกัดการอ้างอิง DEX ได้
- นำโค้ดที่ไม่ได้ใช้ออกด้วย R8
- เปิดใช้การลดขนาดโค้ดเพื่อเรียกใช้ R8 สำหรับบิลด์ที่เผยแพร่ เปิดใช้การย่อเพื่อช่วยให้มั่นใจว่าคุณไม่ได้จัดส่งโค้ดที่ไม่ได้ใช้ไปกับ APK หากกำหนดค่าการลดขนาดโค้ดอย่างถูกต้อง ระบบจะนำโค้ดและทรัพยากรที่ไม่ได้ใช้ออกจากทรัพยากร Dependency ด้วย
การใช้เทคนิคเหล่านี้จะช่วยลดขนาดโดยรวมของ APK และหลีกเลี่ยงความจำเป็นในการใช้ Multidex ในแอปได้
กำหนดค่าแอปสำหรับ Multidex
หมายเหตุ: หากตั้งค่าminSdkVersion เป็น 21 ขึ้นไป ระบบจะเปิดใช้ Multidex โดยค่าเริ่มต้น และคุณไม่จำเป็นต้องใช้ไลบรารี Multidex
หากตั้งค่า minSdkVersion เป็น 20 หรือต่ำกว่า คุณต้องใช้ไลบรารี Multidex และทำการแก้ไขต่อไปนี้ในโปรเจ็กต์แอป
-
แก้ไขไฟล์
build.gradleระดับโมดูลเพื่อเปิดใช้ Multidex และเพิ่มไลบรารี Multidex เป็นทรัพยากร Dependency ดังที่แสดงที่นี่ดึงดูด
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 36 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 36 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
- ดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้ โดยขึ้นอยู่กับว่าคุณลบล้างคลาส
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 จะใช้ Class Loader พิเศษเพื่อค้นหาไฟล์ 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
จากนั้นคุณสามารถประกาศไฟล์นั้นสำหรับประเภทบิลด์ได้ดังนี้
ดึงดูด
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 จะใช้กระบวนการแปลงเป็นไฟล์.dex โดยอัตโนมัติเมื่อติดตั้งใช้งานแอปในอุปกรณ์ที่ใช้ Android 5.0 (ระดับ API 21) ขึ้นไป
อย่างไรก็ตาม หากคุณเรียกใช้บิลด์ Gradle จากบรรทัดคำสั่ง คุณจะต้องตั้งค่า minSdkVersion เป็น 21 ขึ้นไปเพื่อเปิดใช้กระบวนการแปลงเป็นไฟล์ .dex
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); ... }