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ố ...
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ừ <project>/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:2.0.0@aar') { 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ó...