Android 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
如有轉載,請聲明出處: 時之沙: http://blog.csdn.net/t12x3456 (來自時之沙的csdn博客)
隨著應用不斷叠代,業務線的擴展,應用越來越大(比方集成了各種第三方sdk或者公共支持的jar包,項目耦合性高,反復作用的類越來越多),相信非常多人都遇到過例如以下的錯誤:
UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167) at com.android.dx.merge.DexMerger.merge(DexMerger.java:188) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287) at com.android.dx.command.dexer.Main.run(Main.java:230) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103)
沒錯。你的應用中的Dex 文件方法數超過了最大值65536的上限,簡單來說,應用爆棚了.
那麽讓我們看一下為什麽會引起這種錯誤:
在Android系統中。一個App的全部代碼都在一個Dex文件裏面。Dex是一個相似Jar的存儲了多有Java編譯字節碼的歸檔文件。
由於Android系統使用Dalvik虛擬機,所以須要把使用Java Compiler編譯之後的class文件轉換成Dalvik能夠運行的class文件。
這裏須要強調的是。Dex和Jar一樣是一個歸檔文件,裏面仍然是Java代碼相應的字節碼文件。
當Android系統啟動一個應用的時候,有一步是對Dex
DexOpt的運行過程是在第一次載入Dex文件的時候運行的。
這個過程會生成一個ODEX文件,即Optimised Dex。運行ODex的效率會比直接運行Dex文件的效率要高非常多。 可是在早期的Android系統中,DexOpt的LinearAlloc存在著限制: Android 2.2和2.3的緩沖區僅僅有5MB。Android 4.x提高到了8MB或16MB。
當方法數量過多導致超出緩沖區大小時,會造成dexopt崩潰,導致無法安裝.
另外由於DEX文件格式限制。一個DEX文件裏method個數採用使用原生類型short來索引文件裏的方法,也就是最多表達65536個method,field/class的個數也均有此限制。對於DEX文件,則是將project所需全部class文件合而且壓縮到一個DEX文件期間,也就是Android打包的DEX過程中。 單個DEX文件可被引用的方法總數被限制為65536(自己開發以及所引用的Android Framework和第三方類庫的代碼).
眼下比較經常使用的方法:(1) 應用插件化,比方使用我正在參與開發的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,假設有建議或者相關的問題,歡迎到Github上積極參與. (2) 切割Dex,多project: 把所須要的.class文件或者是Jar文件和一些源代碼一起編譯生成一個Jar文件。然後使用Android SDK提供的dx工具把Jar文件轉成Dex文件。
(可參考facebook:https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,這裏邊還能夠看到在2.3上動態改變LinearAlloc緩沖的解決思路) 這兩種方法並不沖突,插件化除了解決應用爆棚。還有非常多其它的長處,能夠看我之前的文章,不再復述.
當然,Google看來也意識到了眼下應用方法數爆棚的問題, 眼下在已經在API 21中提供了通用的解決方式。那就是android-support-multidex.jar. 這個jar包最低能夠支持到API 4的版本號(Android L及以上版本號會默認支持mutidex).
讓我們看一下怎樣應用android-support-multidex.jar(下面都以在Anroid studio中的使用為例,使用eclipse開發須要安裝gradle插件。其它基本上相同):
afterEvaluate { tasks.matching { it.name.startsWith(‘dex‘) }.each { dx -> if (dx.additionalParameters == null) { dx.additionalParameters = [] } dx.additionalParameters += ‘--multi-dex‘ // enable multidex // optional // dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() // enable the main-dex-list } }
可是默認的Dalvik 類載入器僅僅會尋找classes.dex,所以須要將它們進行合並才幹使得被識別
當然。如今有了android.support.multidex.jar的支持,一切都會變得非常easy,首先我們看一下相關源代碼的文件夾,詳細的原理分析我會在之後的文章中進行解說:
android/support/multidex/BuildConfig.class android/support/multidex/MultiDex$V14.class android/support/multidex/MultiDex$V19.class android/support/multidex/MultiDex$V4.class android/support/multidex/MultiDex.class android/support/multidex/MultiDexApplication.class android/support/multidex/MultiDexExtractor$1.class android/support/multidex/MultiDexExtractor.class android/support/multidex/ZipUtil$CentralDirectory.class android/support/multidex/ZipUtil.class
詳細集成:
將例如以下配置加入project build.gradle中
android { defaultConfig { // Enabling multidex support. multiDexEnabled true } } dependencies { compile ‘com.google.android:multidex:0.1‘}
MultiDex實現原理:
Apk在運行的時候。有一個dexpathlist,而Multidex的源代碼中,會依據你的系統版本號號對dexpathlist做改動。將全部的dex都加入到dexpathlist中.
接下來集成有兩個步驟:
一. 從sdk\extras\android\support\multidex\library\libs 文件夾將android-support-multidex.jar導入project中
二. 假設你的project中已經含有Application類,那麽讓它繼承android.support.multidex.MultiDexApplication類,
假設你的Application已經繼承了其它類而且不想做改動,那麽還有第二種使用方式,覆寫attachBaseContext()方法:
public class MyApplication extends FooApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
最後給出build.gradle中的完整配置:
android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 ... // Enabling multidex support. multiDexEnabled true } ... } dependencies { compile ‘com.android.support:multidex:1.0.0‘ }
使用MutiDex的主意事項
一. 假設你繼承了MutiDexApplication或者覆寫了Application中的attachBaseContext()方法.
Application類中邏輯的註意事項:
Application 中的靜態全局變量會比MutiDex的 instal()方法優先載入,所以建議避免在Application類中使用靜態變量引用main classes.dex文件以外dex文件裏的類。能夠依據例如以下所看到的的方式進行改動:
@Override public void onCreate() { super.onCreate(); final Context mContext = this; new Runnable() { @Override public void run() { // put your logic here! // use the mContext instead of this here } }.run(); }
二. 盡管Google攻克了應用總方法數限制的問題,但並不意味著開發人員能夠隨意擴大項目規模。
Multidex仍有一些限制:
- DEX文件安裝到設備的過程非常復雜。假設第二個DEX文件太大,可能導致應用無響應。此時應該使用ProGuard減小DEX文件的大小。
- 由於Dalvik linearAlloc的Bug,應用可能無法在Android 4.0之前的版本號啟動。假設你的應用要支持這些版本號就要多運行測試。
- 相同由於Dalvik linearAlloc的限制。假設請求大量內存可能導致崩潰。
Dalvik linearAlloc是一個固定大小的緩沖區。在應用的安裝過程中。系統會運行一個名為dexopt的程序為該應用在當前機型中運行做準備。dexopt使用LinearAlloc來存儲應用的方法信息。
Android 2.2和2.3的緩沖區僅僅有5MB。Android 4.x提高到了8MB或16MB。
當方法數量過多導致超出緩沖區大小時。會造成dexopt崩潰。
- Multidex構建工具還不支持指定哪些類必須包括在首個DEX文件裏,因此可能會導致某些類庫(比如某個類庫須要從原生代碼訪問Java代碼)無法使用。
避免應用過大、方法過多仍然是Android開發人員要註意的問題。Mihai Parparita的開源項目dex-method-counts能夠用於統計APK中每一個包的方法數量。
通常開發人員自己的代碼非常難達到這個方案數量限制。但隨著第三方類庫的加入,方法數就會迅速膨脹。因此選擇合適的類庫對Android開發人員來說尤為重要。
開發人員應該避免使用Google Guava這種類庫。它包括了13000多個方法。
盡量使用專為移動應用設計的Lite/Android版本號類庫。或者使用小類庫替換大類庫,比如用Google-gson替換Jackson JSON。
而對於Google Protocol Buffers這種數據交換格式。其標準實現會自己主動生成大量的方法。採用Square Wire的實現則能夠非常好地解決此問題。
常見問題
Error:Execution failed for task ‘:app:dexDebug‘. > com.android.ide.common.internal.LoggedErrorException: Failed to run command: $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex ... Error Code: 2 Output: 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:322) at com.android.dx.command.dexer.Main.run(Main.java:228) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103)
對於dex 的--multi-dex 選項設置與預編譯的libraryproject有沖突,因此假設你的應用中包括引用的liraryproject,須要將預編譯設置為false:
android { // ... dexOptions { preDexLibraries = false } }
OutOfMemoryError: Java heap space
當運行時假設看到例如以下錯誤:
UNEXPECTED TOP-LEVEL ERROR: java.lang.OutOfMemoryError: Java heap space
在dexOptions中有一個字段用來添加java堆內存大小:
android { // ... dexOptions { javaMaxHeapSize "2g" } }
使用eclipse的開發人員能夠參考Google官方文檔
https://developer.android.com/tools/building/multidex.html#mdex-gradle
須要安裝支持gradle構建的插件,下載地址:
http://dist.springsource.com/release/TOOLS/gradle (眼下可能須要FQ)
1. MutiDex 官方文檔: https://developer.android.com/reference/android/support/multidex/MultiDex.html
2. http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html
另附android -support-mutidex.jar下載地址: http://download.csdn.net/detail/t12x3456/8143383
補充註意事項:
com.android.dex.DexException: Multiple dex files define L{package}/BuildConfig;假設遇到這個錯誤請進行例如以下檢查:
1. 主project與依賴libraryproject包名是否反復
2. 檢查主project與依賴libraryproject是否含有反復的support.jar或者其它jar包
解決方式:
1. 改動libraryproject包名
2. 刪除反復jar包
3.手工加入lib包,添加例如以下配置
dependencies { compile fileTree(dir: ‘libs‘, include: [‘*.jar‘]) compile project(‘:lib-project-module‘) }
Android 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚