Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android dex file cannot exceed 64K錯誤,使用Mutidex解決Dex超出方法數的限制

Android dex file cannot exceed 64K錯誤,使用Mutidex解決Dex超出方法數的限制

編輯:關於Android編程

隨著應用不斷迭代,業務線的擴展,應用越來越大(比如集成了各種第三方sdk或者公共支持的jar包,項目耦合性高,重復作用的類越來越多),相信很多人都遇到過如下的錯誤:

  1. UNEXPECTEDTOP-LEVELEXCEPTION:
  2. java.lang.IllegalArgumentException:methodIDnotin[0,0xffff]:65536
  3. atcom.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)
  4. atcom.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)
  5. atcom.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)
  6. atcom.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)
  7. atcom.android.dx.merge.DexMerger.merge(DexMerger.java:188)
  8. atcom.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)
  9. atcom.android.dx.command.dexer.Main.runMonoDex(Main.java:287)
  10. atcom.android.dx.command.dexer.Main.run(Main.java:230)
  11. atcom.android.dx.command.dexer.Main.main(Main.java:199)
  12. atcom.android.dx.command.Main.main(Main.java:103)

沒錯,你的應用中的Dex 文件方法數超過了最大值65536的上限,簡單來說,應用爆棚了.

那麼讓我們看一下為什麼會引起這種錯誤:

 

Android系統中,一個App的所有代碼都在一個Dex文件裡面。Dex是一個類似Jar的存儲了多有Java編譯字節碼的歸檔文件。因為Android系統使用Dalvik虛擬機,所以需要把使用Java Compiler編譯之後的class文件轉換成Dalvik能夠執行的class文件。這裡需要強調的是,DexJar一樣是一個歸檔文件,裡面仍然是Java代碼對應的字節碼文件。當Android系統啟動一個應用的時候,有一步是對Dex進行優化,這個過程有一個專門的工具來處理,叫DexOptDexOpt的執行過程是在第一次加載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來索引文件中的方法,也就是4個字節共計最多表達65536個method,field/class的個數也均有此限制。對於DEX文件,則是將工程所需全部class文件合並且壓縮到一個DEX文件期間,也就是Android打包的DEX過程中, 單個DEX文件可被引用的方法總數被限制為65536(自己開發以及所引用的Android Framework和第三方類庫的代碼).

目前比較常用的方法:(1) 應用插件化,比如使用我正在參與開發的插件化框架 :https://github.com/singwhatiwanna/dynamic-load-apk,如果有建議或者相關的問題,歡迎到Github上積極參與. (2) 分割Dex,多工程: 把所需要的.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插件,其他基本上相同):

 

首先可以用--multi-dex配置(build.gradle)進行解決,生成的Apk中將包含多個dex文件,比如classes.dex, classes2.dex. 如下,需要對build.gradle進行修改:
  1. afterEvaluate{
  2. tasks.matching{
  3. it.name.startsWith('dex')
  4. }.each{dx->
  5. if(dx.additionalParameters==null){
  6. dx.additionalParameters=[]
  7. }
  8. dx.additionalParameters+='--multi-dex'//enablemultidex
  9.  
  10. //optional
  11. //dx.additionalParameters+="--main-dex-list=$projectDir/".toString()//enablethemain-dex-list
  12. }
  13. }


但是默認的Dalvik 類加載器只會尋找classes.dex,所以需要將它們進行合並才能使得被識別

當然,現在有了android.support.multidex.jar的支持,一切都會變得非常簡單,首先我們看一下相關源碼的目錄,具體的原理分析我會在之後的文章中進行講解:
  1. android/support/multidex/BuildConfig.class
  2. android/support/multidex/MultiDex$V14.class
  3. android/support/multidex/MultiDex$V19.class
  4. android/support/multidex/MultiDex$V4.class
  5. android/support/multidex/MultiDex.class
  6. android/support/multidex/MultiDexApplication.class
  7. android/support/multidex/MultiDexExtractor$1.class
  8. android/support/multidex/MultiDexExtractor.class
  9. android/support/multidex/ZipUtil$CentralDirectory.class
  10. android/support/multidex/ZipUtil.class
具體集成:
將如下配置加入工程build.gradle中
  1. android{
  2. defaultConfig{
  3. //Enablingmultidexsupport.
  4. multiDexEnabledtrue
  5. }
  6. }
  7. dependencies{compile'com.google.android:multidex:0.1'}


接下來集成有兩個步驟:
  1. publicclassMyApplicationextendsFooApplication{
  2. @Override
  3. protectedvoidattachBaseContext(Contextbase){
  4. super.attachBaseContext(base);
  5. MultiDex.install(this);
  6. }
  7. }

最後給出build.gradle中的完整配置:

  1. android{
  2. compileSdkVersion21
  3. buildToolsVersion"21.1.0"
  4.  
  5. defaultConfig{
  6. ...
  7. minSdkVersion14
  8. targetSdkVersion21
  9. ...
  10.  
  11. //Enablingmultidexsupport.
  12. multiDexEnabledtrue
  13. }
  14. ...
  15. }
  16.  
  17. dependencies{
  18. compile'com.android.support:multidex:1.0.0'
  19. }

使用MutiDex的主意事項

 

一. 如果你繼承了MutiDexApplication或者覆寫了Application中的attachBaseContext()方法.

Application類中邏輯的注意事項:

Application中的靜態全局變量會比MutiDexinstal()方法優先加載,所以建議避免在Application類中使用靜態變量引用main classes.dex文件以外dex文件中的類,可以根據如下所示的方式進行修改:

  1. @Override
  2. publicvoidonCreate(){
  3. super.onCreate();
  4.  
  5. finalContextmContext=this;
  6. newRunnable(){
  7.  
  8. @Override
  9. publicvoidrun(){
  10. //putyourlogichere!
  11. //usethemContextinsteadofthishere
  12. }
  13. }.run();
  14. }

二.雖然Google解決了應用總方法數限制的問題,但並不意味著開發者可以任意擴大項目規模。Multidex仍有一些限制:

  1. DEX文件安裝到設備的過程非常復雜,如果第二個DEX文件太大,可能導致應用無響應。此時應該使用ProGuard減小DEX文件的大小。
  2. 由於Dalvik linearAlloc的Bug,應用可能無法在Android 4.0之前的版本啟動,如果你的應用要支持這些版本就要多執行測試。
  3. 同樣因為Dalvik linearAlloc的限制,如果請求大量內存可能導致崩潰。Dalvik linearAlloc是一個固定大小的緩沖區。在應用的安裝過程中,系統會運行一個名為dexopt的程序為該應用在當前機型中運行做准備。dexopt使用LinearAlloc來存儲應用的方法信息。Android 2.2和2.3的緩沖區只有5MB,Android 4.x提高到了8MB或16MB。當方法數量過多導致超出緩沖區大小時,會造成dexopt崩潰。
  4. 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的實現則可以很好地解決此問題。


常見問題

 

DexException: Library dex files are not supported in multi-dex mode,你可能會見到如下的錯誤:
  1. Error:Executionfailedfortask':app:dexDebug'.
  2. >com.android.ide.common.internal.LoggedErrorException:Failedtoruncommand:
  3. $ANDROID_SDK/build-tools/android-4.4W/dx--dex--num-threads=4--multi-dex
  4. ...
  5. ErrorCode:
  6. 2
  7. Output:
  8. UNEXPECTEDTOP-LEVELEXCEPTION:
  9. com.android.dex.DexException:Librarydexfilesarenotsupportedinmulti-dexmode
  10. atcom.android.dx.command.dexer.Main.runMultiDex(Main.java:322)
  11. atcom.android.dx.command.dexer.Main.run(Main.java:228)
  12. atcom.android.dx.command.dexer.Main.main(Main.java:199)
  13. atcom.android.dx.command.Main.main(Main.java:103)

對於dex 的--multi-dex 選項設置與預編譯的library工程有沖突,因此如果你的應用中包含引用的lirary工程,需要將預編譯設置為false:
  1. android{
  2. //...
  3. dexOptions{
  4. preDexLibraries=false
  5. }
  6. }


OutOfMemoryError: Java heap space

當運行時如果看到如下錯誤:
  1. UNEXPECTEDTOP-LEVELERROR:
  2. java.lang.OutOfMemoryError:Javaheapspace
在dexOptions中有一個字段用來增加java堆內存大小:

  1. android{
  2. //...
  3. dexOptions{
  4. javaMaxHeapSize"2g"
  5. }
  6. }

補充注意事項:

com.android.dex.DexException:MultipledexfilesdefineL{package}/BuildConfig; 如果遇到這個錯誤請進行如下檢查:

1. 主工程與依賴library工程包名是否重復
2. 檢查主工程與依賴library工程是否含有重復的support.jar或者其他jar包

解決方案:

1. 修改library工程包名

2. 刪除重復jar包

3.手工添加lib包,增加如下配置

  1. dependencies{
  2. compilefileTree(dir:'libs',include:['*.jar'])
  3. compileproject(':lib-project-module')
  4. }
  5.  

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved