Bật multidex cho các ứng dụng có hơn 64.000 phương thức

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Khi ứng dụng của bạn và các thư viện mà ứng dụng tham chiếu vượt quá 65.536 phương thức, bạn gặp phải lỗi bản dựng cho biết ứng dụng của bạn đã đạt đến giới hạn của cấu trúc bản dựng Android:

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

Các phiên bản cũ hơn của hệ thống bản dựng báo cáo một lỗi khác, đó là dấu hiệu cho cùng một vấn đề:

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

Cả hai điều kiện lỗi này đều hiển thị một số chung: 65536. Con số này đại diện cho tổng số tham chiếu có thể được gọi bằng mã trong một tệp mã byte Dalvik Executable (DEX) duy nhất. Trang này giải thích cách vượt qua giới hạn này bằng cách bật cấu hình ứng dụng được gọi là multidex. Cấu hình này cho phép ứng dụng của bạn tạo và đọc nhiều tệp DEX.

Giới thiệu về giới hạn tham chiếu 64K

Tệp ứng dụng Android (APK) chứa các tệp mã byte ở dạng tệp Dalvik có thể thực thi (DEX), chứa mã đã biên dịch dùng để chạy ứng dụng của bạn. Thông số có thể thực thi Dalvik giới hạn tổng số phương thức có thể được tham chiếu trong một tệp DEX ở mức 65.536—bao gồm các phương thức khung Android, phương thức thư viện và phương thức trong mã của riêng bạn. Trong bối cảnh khoa học máy tính, thuật ngữ Kilo, K, biểu thị 1024 (hoặc 2^10). Vì 65.536 bằng 64 X 1024, nên giới hạn này được gọi là "giới hạn tham chiếu 64K".

Hỗ trợ Multidex trước Android 5.0

Các phiên bản của nền tảng trước Android 5.0 (API cấp độ 21) sử dụng thời gian chạy Dalvik để thực thi mã ứng dụng. Theo mặc định, Dalvik giới hạn các ứng dụng thành một tệp mã byte classes.dex duy nhất cho mỗi APK. Để khắc phục hạn chế này, bạn có thể thêm thư viện multidex vào tệp build.gradle ở cấp mô-đun:

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

Để xem các phiên bản hiện tại của thư viện này, hãy xem thông tin về Multidex trên trang phiên bản.

Nếu bạn không sử dụng AndroidX, hãy thêm phần phụ thuộc thư viện hỗ trợ không còn được dùng sau đây thay thế:

Groovy

dependencies {
    implementation 'com.android.support:multidex:1.0.3'
}

Kotlin

dependencies {
    implementation("com.android.support:multidex:1.0.3")
}

Thư viện này trở thành một phần của tệp DEX chính của ứng dụng, sau đó quản lý quyền truy cập vào các tệp DEX bổ sung và mã chứa tệp này. Bạn có thể xem thêm thông tin chi tiết bên dưới trong phần về cách định cấu hình ứng dụng cho multidex.

Hỗ trợ Multidex cho Android 5.0 trở lên

Android 5.0 (API cấp độ 21) trở lên sử dụng thời gian chạy có tên là ART, vốn hỗ trợ việc tải nhiều tệp DEX từ các tệp APK. ART thực hiện quá trình tổng hợp trước tại thời điểm cài đặt ứng dụng. Tính năng này quét tìm các tệp classesN.dex và tổng hợp các tệp đó vào một tệp .oat duy nhất để thực thi trên thiết bị Android. Do đó, nếu minSdkVersion của bạn có từ 21 multidex trở lên được bật theo mặc định, thì bạn không cần sử dụng thư viện multidex nữa.

Để biết thêm thông tin về thời gian chạy Android 5.0, hãy đọc ART và Dalvik.

Lưu ý: Khi chạy ứng dụng của bạn bằng Android Studio, bản dựng sẽ được tối ưu hóa cho các thiết bị mục tiêu mà bạn triển khai. Điều này bao gồm việc bật multidex khi các thiết bị mục tiêu đang chạy Android 5.0 trở lên. Vì tính năng tối ưu hóa này chỉ được áp dụng khi triển khai ứng dụng bằng Android Studio, nên bạn vẫn có thể cần phải định cấu hình bản phát hành cho multidex để tránh giới hạn 64K.

Tránh giới hạn 64K

Trước khi định cấu hình ứng dụng để cho phép sử dụng phương thức đối chiếu 64K trở lên, bạn nên thực hiện các bước để giảm tổng số tệp đối chiếu được mã ứng dụng của bạn gọi, bao gồm cả các phương thức được xác định theo mã ứng dụng của bạn hoặc thư viện đi kèm. Các chiến lược sau đây có thể giúp bạn tránh đạt đến giới hạn tham chiếu DEX:

  • Xem lại phần phụ thuộc trực tiếp và bắc cầu của ứng dụng – Đảm bảo mọi phần phụ thuộc trong thư viện mà bạn đưa vào ứng dụng đều được sử dụng sao cho nhiều hơn số lượng mã được thêm vào ứng dụng. Một biện pháp chống mẫu thường gặp là thêm một thư viện rất lớn vì một số phương pháp sử dụng tiện ích tỏ ra hữu ích. Thông thường, việc giảm phần phụ thuộc mã ứng dụng có thể giúp bạn tránh giới hạn tệp tham chiếu DEX.
  • Xóa mã không sử dụng trong R8Bật tính năng rút gọn mã để chạy R8 trong bản phát hành. Khi bật tính năng rút gọn, bạn không phải vận chuyển mã không sử dụng đến APK.

Việc sử dụng những kỹ thuật này có thể giúp bạn không cần phải bật multidex trong ứng dụng đồng thời cũng giảm kích thước tổng thể của tệp APK.

Định cấu hình ứng dụng của bạn cho multidex

Nếu minSdkVersion của bạn được đặt thành 21 trở lên, thì Multidex sẽ được bật theo mặc định và bạn không cần có thư viện Multidex.

Tuy nhiên, nếu minSdkVersion của bạn được đặt thành 20 trở xuống, thì bạn phải sử dụng thư viện multidex và thực hiện những sửa đổi sau đối với dự án ứng dụng:

  1. Sửa đổi tệp build.gradle cấp mô-đun để bật multidex và thêm thư viện multidex dưới dạng phần phụ thuộc, như minh họa dưới đây:

    Groovy

    android {
        defaultConfig {
            ...
            minSdkVersion 15
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    

    Kotlin

    android {
        defaultConfig {
            ...
            minSdk = 15
            targetSdk = 28
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
    
  2. Tùy thuộc vào việc bạn ghi đè loại Application hay không, hãy thực hiện một trong những thao tác sau:
    • Nếu bạn không ghi đè loại Application, hãy chỉnh sửa tệp kê khai của bạn để đặt android:name trong thẻ <application> như sau:

      <?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>
      
    • Nếu bạn ghi đè loại Application, hãy thay đổi loại này để mở rộng MultiDexApplication (nếu được) như sau:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Hoặc nếu bạn ghi đè loại Application nhưng không thể thay đổi loại cơ sở, thì bạn có thể ghi đè phương thức attachBaseContext() và gọi MultiDex.install(this) thay thế để bật 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);
        }
      }
      

      Lưu ý: Không thực thi MultiDex.install() hoặc bất kỳ mã nào khác thông qua tính năng phản chiếu hoặc JNI trước khi MultiDex.install() hoàn tất. Quá trình theo dõi Multidex sẽ không tuân theo các lệnh gọi đó, gây ra ClassNotFoundException hoặc xác minh lỗi do phân vùng lớp không hợp lệ giữa các tệp DEX.

Giờ đây, khi bạn tạo ứng dụng, các công cụ xây dựng Android sẽ tạo một tệp DEX chính (classes.dex) và hỗ trợ các tệp DEX (classes2.dex, classes3.dex, v.v.) khi cần. Sau đó, hệ thống xây dựng sẽ kết hợp tất cả các tệp DEX vào APK của bạn.

Trong thời gian chạy, các API multidex sử dụng một trình tải lớp đặc biệt để tìm kiếm tất cả các tệp DEX có sẵn cho phương thức của bạn (thay vì chỉ tìm kiếm trong tệp classes.dex chính).

Các hạn chế của thư viện multidex

Thư viện Multidex có một số giới hạn mà bạn nên biết và thử nghiệm khi kết hợp thư viện đó vào cấu hình xây dựng của ứng dụng:

  • Việc cài đặt các tệp DEX trong quá trình khởi động vào phân vùng dữ liệu của thiết bị rất phức tạp và có thể dẫn đến lỗi Ứng dụng không phản hồi (ANR) nếu tệp DEX phụ có kích thước lớn. Để tránh vấn đề này, hãy bật tính năng rút gọn mã để giảm thiểu kích thước của các tệp DEX và xóa các phần mã không sử dụng.
  • Khi chạy trên các phiên bản trước Android 5.0 (API cấp độ 21), việc sử dụng Multidex là không đủ để hoạt động xung quanh giới hạn phân bổ tuyến tính (vấn đề 78035). Giới hạn này đã tăng trong Android 4.0 (API cấp độ 14), nhưng điều đó không giải quyết được hoàn toàn vấn đề này. Trong các phiên bản Android thấp hơn 4.0, bạn có thể đạt đến giới hạn phân bổ tuyến tính trước khi đạt đến giới hạn chỉ số DEX. Vì vậy, nếu bạn đang nhắm mục tiêu đến các cấp độ API thấp hơn 14, hãy kiểm tra kỹ các phiên bản của nền tảng đó vì ứng dụng của bạn có thể gặp vấn đề khi khởi động hoặc khi một nhóm loại cụ thể được tải.

    Tính năng rút gọn mã có thể giảm hoặc loại bỏ các vấn đề này.

Khai báo các loại bắt buộc trong tệp DEX chính

Khi tạo từng tệp DEX cho một ứng dụng multidex, các công cụ xây dựng sẽ thực hiện việc ra quyết định phức tạp để xác định loại nào là cần thiết trong tệp DEX chính để ứng dụng của bạn có thể khởi động thành công. Nếu tất cả các loại bắt buộc trong quá trình khởi động không được cung cấp trong tệp DEX chính, thì ứng dụng của bạn sẽ gặp lỗi java.lang.NoClassDefFoundError.

Điều này không nên xảy ra cho mã được truy cập trực tiếp từ mã ứng dụng của bạn vì các công cụ xây dựng nhận ra các đường dẫn mã đó, nhưng điều này có thể xảy ra khi đường dẫn mã hiển thị ít hơn, chẳng hạn như khi thư viện bạn sử dụng có phần phụ thuộc phức tạp. Ví dụ: nếu mã sử dụng phương thức gọi nội dung hoặc gọi của phương thức Java từ mã gốc, thì các lớp đó có thể không được nhận dạng là bắt buộc trong tệp DEX chính.

Vì vậy, nếu nhận được java.lang.NoClassDefFoundError, thì bạn phải chỉ định các loại bổ sung này theo cách thủ công là bắt buộc trong tệp DEX chính bằng cách khai báo các loại đó bằng thuộc tính multiDexKeepFile hoặc multiDexKeepProguard trong loại bản dựng của bạn. Nếu một loại được so khớp trong tệp multiDexKeepFile hoặc multiDexKeepProguard, thì loại đó sẽ được thêm vào tệp DEX chính.

thuộc tính multiDexKeepFile

Tệp bạn chỉ định trong multiDexKeepFile phải chứa một loại trên mỗi dòng, ở định dạng com/example/MyClass.class. Ví dụ: bạn có thể tạo tệp có tên multidex-config.txt như sau:

com/example/MyClass.class
com/example/MyOtherClass.class

Sau đó, bạn có thể khai báo tệp đó cho loại bản dựng như sau:

Groovy

android {
    buildTypes {
        release {
            multiDexKeepFile file('multidex-config.txt')
            ...
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            multiDexKeepFile = file('multidex-config.txt')
            ...
        }
    }
}

Hãy nhớ rằng Gradle đọc các đường dẫn liên quan đến tệp build.gradle, vì vậy, ví dụ ở trên sẽ hoạt động nếu multidex-config.txt ở cùng thư mục với tệp build.gradle.

thuộc tính multiDexKeepFile

Tệp multiDexKeepProguard sử dụng định dạng giống như Proguard và hỗ trợ toàn bộ ngữ pháp của Proguard. Để biết thêm thông tin về định dạng của Proguard và ngữ pháp, hãy xem phần Tùy chọn Keep trong hướng dẫn sử dụng Proguard.

Tệp mà bạn chỉ định trong multiDexKeepProguard phải chứa các tùy chọn -keep trong mọi cú pháp ProGuard hợp lệ. Ví dụ: -keep com.example.MyClass.class. Bạn có thể tạo tệp có tên multidex-config.pro trông như sau:

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

Nếu bạn muốn chỉ định tất cả các loại trong một gói, tệp sẽ trông giống như sau:

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

Sau đó, bạn có thể khai báo tệp đó cho loại bản dựng như sau:

Groovy

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

Kotlin

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

Tối ưu hóa tính năng tìm kiếm multidex trong các bản dựng phát triển

Cấu hình multidex yêu cầu phải tăng đáng kể thời gian xây dựng vì hệ thống bản dựng phải đưa ra các quyết định phức tạp về việc phải đưa loại nào vào tệp DEX chính và loại nào có thể được đưa vào tệp DEX phụ. Điều này có nghĩa là các bản dựng tăng dần bằng cách sử dụng Multidex thường mất nhiều thời gian hơn và có thể làm chậm quá trình phát triển của bạn.

Để giảm thiểu thời gian xây dựng tăng dần, bạn nên sử dụng tính năng chuyển sang định dạng .dex để sử dụng lại kết quả multidex trong các bản dựng. Việc chuyển sang định dạng .dex phụ thuộc vào định dạng ART chỉ có trên Android 5.0 (API cấp độ 21) trở lên. Nếu bạn đang sử dụng Android Studio 2.3 trở lên, IDE sẽ tự động sử dụng tính năng này khi bạn triển khai ứng dụng trên thiết bị chạy Android 5.0 (API cấp độ 21) trở lên.

Mẹo: Trình bổ trợ Android cho Gradle 3.0.0 trở lên có thêm các điểm cải tiến để tối ưu hóa tốc độ xây dựng, chẳng hạn như khả năng chuyển định dạng .dex theo từng loại (để chỉ các loại mà bạn sửa đổi sẽ được chuyển lại sang định dạng .dex). Nhìn chung, để có trải nghiệm phát triển tốt nhất, bạn nên luôn nâng cấp lên phiên bản Android Studio và plugin Android mới nhất.

Tuy nhiên, nếu bạn đang chạy các bản dựng Gradle từ dòng lệnh, bạn cần phải đặt minSdkVersion thành 21 trở lên để bật tính năng chuyển sang định dạng .dex. Chiến lược hữu ích để duy trì các tùy chọn cài đặt cho bản dựng xây dựng là tạo hai phiên bản ứng dụng bằng cách sử dụng phiên bản sản phẩm : phiên bản phát triển và phiên bản phát hành có những giá trị khác nhau cho minSdkVersion , như được minh họa dưới đây.

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 what you set for
            // 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 what you set for
            // 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")
}

Để tìm hiểu thêm về các chiến lược giúp cải thiện tốc độ xây dựng (từ Android Studio hoặc dòng lệnh), hãy đọc bài viết Tối ưu hóa Tốc độ Xây dựng. Để biết thêm thông tin về việc sử dụng các biến thể xây dựng, hãy xem phần Định cấu hình Biến thể Xây dựng.

Mẹo: Giờ đây, khi có các biến thể xây dựng khác nhau cho các nhu cầu multidex khác nhau, bạn cũng có thể cung cấp một tệp kê khai khác nhau cho mỗi biến thể (để chỉ một biến thể dành cho API cấp độ 20 trở xuống sẽ thay đổi tên thẻ <application>) hoặc tạo một loại con Application khác cho mỗi biến thể (để chỉ có một biến thể cho API cấp độ 20 trở xuống sẽ mở rộng loại MultiDexApplication hoặc gọi MultiDex.install(this) ).

Thử nghiệm ứng dụng Multidex

Khi bạn viết kiểm thử đo lường cho các ứng dụng multidex, bạn không cần phải định cấu hình thêm nếu sử dụng MonitoringInstrumentation (hoặc khả năng đo lường AndroidJUnitRunner) Nếu sử dụng một Instrumentation khác, bạn phải ghi đè phương thức onCreate() của nó bằng mã sau:

Kotlin

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

Java

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