Giải Pháp MultiDex Cho Giới Hạn 64k Trong Dalvik

Hầu hết các nhà phát triển android đều biết một sự thật buồn - Dalvik virtual machine của Android đã được sử dụng bởi các ứng dụng và một vài service của hệ thống có một giới hạn lớn - single .dex file (bytecode được chạy bởi Dalvik VM) có một giới hạn 64k (chính xác 65536 methods). Con số 64k sẽ chẳng có ý nghĩa gì nếu như họ chưa phải đối mặt với giới hạn này. Nhưng trong trường hợp ứng dụng của bạn đang viết hay thư viện bạn đang customize có chứ nhiều method và bạn gọi một method đặt đặt tại vị trí sau 65536 ứng dụng của bạn sẽ bị crash với error

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

Trong bài viết tuyệt vời DEX Sky’s the limit? No, 65K methods is bạn có thể tìm thấy nhiều chi tiết hơn và giải thích rõ ràng hơn về vấn đề này. 64k Là một con số lớn. Bạn có cần thực sự quan tâm về giớ hạn này ? Android phát triển rất nhanh. Ngoài ra cũng từ những nhà phát triển bên thứ 3. Các thư viện phát triển, Google release Play Services mới . Trong mỗi version có hàng trăm , thậm chí hàng nghìn phương thức nhiều hơn rất nhiêu so với version trước. Giả sử rằng chúng ta muốn tạo ra một ứng dụng MVP với :

  • REST (json) client
  • Cấu trức project rõ ràng
  • Có các animation và UI tuyệt vời
  • Login bằng Facebook
  • Tương thích với android version 4 và 5

Chúng ta tạo một ứng dụng đơn giản trong android studio với một Blank Activity và minSdkVersion=”15” bây giờ tôi sẽ add một vài libraries. ở đây bạn sẽ nhìn thầy toàn bộ dependencies list từ

/app/build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.android.support:support-v4:21.+'
    compile 'com.android.support:support-v13:21.+'
    compile 'com.android.support:appcompat-v7:21.0.+'
    compile 'com.android.support:palette-v7:+'
    compile 'com.android.support:recyclerview-v7:+'
    compile 'com.google.android.gms:play-services:6.1.+'
    compile 'com.google.guava:guava:18.0'
    compile 'com.google.code.gson:gson:2.3'

    compile 'com.netflix.rxjava:rxjava-android:0.20.5'

    provided 'com.squareup.dagger:dagger-compiler:1.2.2'
    compile 'com.squareup.retrofit:retrofit:1.7.0'
    compile 'com.squareup.dagger:dagger:1.2.2'
    compile 'com.squareup.picasso:picasso:2.3.4'
    compile 'com.squareup:otto:1.3.5'

    compile 'com.jakewharton:butterknife:5.1.2'
    compile 'com.jakewharton.timber:timber:2.4.0'

    compile 'com.newrelic.agent.android:android-agent:3.+'
    compile('com.crashlytics.sdk.android:crashlytics:[email protected]') {
        transitive = true;
    }

    compile 'se.emilsjolander:stickylistheaders:2.5.1'
    compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
    compile 'com.facebook.rebound:rebound:0.3.6'
}

Tiếp theo chúng ta sẽ bắt đầu đếm tất cả các method mà chúng ta đã sử dụng ( lưu ý rằng giới hạn 64k bao gồm cả những method từ tất cả các dependencies đang được sử dụng trong project ). Đầu tiên build/run project (chúng ta phải tạo ra file .apk or tạo ra file .dex ). Bạn có thể thực hiện việc này bằng cách click vào button run trên android studio or bằng cách chạy

$ ./gradlew assembleDebug

Sau khi chúng ta đã tạo ra được file apk. Bây giờ chúng ta bắt đầu phân tích nó. Để làm điều này tôi sử dụng tools dex-method-counts. Sau khi chúng ta đã download về. chúng ta phải chạy 2 câu lệnh

$ ./gradlew assemble
$ ./dex-method-counts path/to/App.apk # or .zip or .dex or directory

Kết Quả Tôi update Google Play Services từ 5 tới 6

Read in 63897 method IDs.
<root>: 63897
    android: 14275
        support: 11454
        	...
    butterknife: 161

    com: 40866
        crashlytics: 631
            ...
        facebook: 221
            ...
        google: 36603
            android: 21839
			...
        newrelic: 2579
            ...
        squareup: 511
            okhttp: 8
            otto: 57
            picasso: 446
            
    io: 1125
        fabric: 1102
            ...
        github: 23
            froger: 23
                hellomultidex: 23
                
    java: 1928

    retrofit: 474

    rx: 3337

    timber: 65
        log: 65
        
Overall method count: 63897

Chúng ta vẫn chưa viết bất kỳ dòng code nào, nhưng dường như chúng ta đã đạt tới giới hạn với 63897 methods Bây giờ, giả sử chúng ta thêm bất kỳ một thư viện nào đó. và chúng ta build lại project :

UNEXPECTED TOP-LEVEL EXCEPTION:
com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
	at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:502)
	at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:283)
	at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:491)
	at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:168)
	at com.android.dx.merge.DexMerger.merge(DexMerger.java:189)
	at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:454)
	at com.android.dx.command.dexer.Main.runMonoDex(Main.java:302)
	at com.android.dx.command.dexer.Main.run(Main.java:245)
	at com.android.dx.command.dexer.Main.main(Main.java:214)
	at com.android.dx.command.Main.main(Main.java:106)

Vâng, nó đã sảy ra, và chúng ta sẽ không biết được thêm bất kỳ dòng code nào Vậy Làm Sao Chúng Tao Có Thể Giải Quyết Vấn Đề Này ??? Tất nhiên trong tất cả các giải pháp đúng nhất là ProGuard nhưng Chúng ta đang làm việc trên MVP và chúng ta phải đối mặt với

FATAL EXCEPTION: main
    java.lang.NoClassDefFoundError: (...)

Google đã có một giải pháp cho chúng ta . Chúng ta có thể chia project của chúng ta nhiều hơn 1 file .dex và load tại runtime. Tuy nhiền cho đến bây giờ quá trình này không phải là đơn giản và rõ ràng (một vài năm trước google đã ccó 1 bài viết về điều này) android.support.multidex Android 5.0 Lollipop release một thư viện support mới đã suất hiện trong android , nó có chứa duy nhất 2 class : MultiDex and MultiDexApplication Việc sử dụng MultiDex là khá đơn giản Configuring project Đầu tiên chúng ta phải cấu hình project của chúng ta , để tách project của chúng ta vào trong nhiều file dex Trong app/build.gradle

afterEvaluate {
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString()
    }
}

Chúng ta có 2 params:

  • --multi-dex : enable cơ chế splitting trong quá trình build
  • --main-dex-list: (không yêu cầu) - file với một list của những class đã được attach trong file dex chính Chúng ta phải attach android-support-multidex.jar library vào trong project . Bạn có thể tìm thấy nó trong .../Android SDK directory/extras/android/support/multidex/library . Chúng ta có 3 cách để load file .dex vào trong project: khai báo MultiDexApplication class in AndroidManifest.xml file:
<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:name="android.support.multidex.MultiDexApplication">
    ...
</application>

**Extend MultiDexApplication trong Application class **

public class HelloMultiDexApplication extends MultiDexApplication {
    @Override
    public void onCreate() {
        super.onCreate();
    }
}
<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:name=".HelloMultiDexApplication">
    ...
</application>

Nếu bạn không thể extends MultiDexApplication chúng ta có thể cài đặt multiple dex files bằng cách overriding attachBaseContext(Context base) method trong class Application

public class HelloMultiDexApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

Nhìn chung việc cấu hình project của chúng ta như vậy là đủ, nhưng nếu bạn build và run project mà gặp phải lỗi sau :

UNEXPECTED TOP-LEVEL EXCEPTION:
	com.android.dex.DexException: Library dex files are not supported in multi-dex mode
		at com.android.dx.command.dexer.Main.runMultiDex(Main.java:337)
		at com.android.dx.command.dexer.Main.run(Main.java:243)
		at com.android.dx.command.dexer.Main.main(Main.java:214)
		at com.android.dx.command.Main.main(Main.java:106)

Bạn nên làm như sau trong app/build.gradle:

android {
//  ...
    dexOptions {
        preDexLibraries = false
    }
}

Đó là tất cả. Cảm ơn bạn đã dành thời gian đọc nó... 😛

All Rights Reserved