天天看點

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚

 随着應用不斷疊代,業務線的擴充,應用越來越大(比如內建了各種第三方sdk或者公共支援的jar包,項目耦合性高,重複作用的類越來越多),相信很多人都遇到過如下的錯誤:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. UNEXPECTED TOP-LEVEL EXCEPTION:  
  2. java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536  
  3. at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)  
  4. at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)  
  5. at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)  
  6. at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)  
  7. at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)  
  8. at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)  
  9. at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)  
  10. at com.android.dx.command.dexer.Main.run(Main.java:230)  
  11. at com.android.dx.command.dexer.Main.main(Main.java:199)  
  12. 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。DexOpt的執行過程是在第一次加載Dex檔案的時候執行的。這個過程會生成一個ODEX檔案,即Optimised Dex。執行ODex的效率會比直接執行Dex檔案的效率要高很多。但是在早期的Android系統中,DexOpt有一個問題,也就是這篇文章想要說明并解決的問題。DexOpt會把每一個類的方法id檢索起來,存在一個連結清單結構裡面。但是這個連結清單的長度是用一個short類型來儲存的,導緻了方法id的數目不能夠超過65536個。當一個項目足夠大的時候,顯然這個方法數的上限是不夠的。盡管在新版本的Android系統中,DexOpt修複了這個問題,但是我們仍然需要對低版本的Android系統做相容.

        目前比較常用的方法:(1) 應用插件化,比如使用我正在參與開發的插件化架構 : https://github.com/singwhatiwanna/dynamic-load-apk ,如果有建議或者相關的問題,歡迎到Github上積極參與. (2) 分割Dex,多工程: 把所需要的.class檔案或者是Jar檔案和一些源碼一起編譯生成一個Jar檔案。然後使用Android SDK提供的dx工具把Jar檔案轉成Dex檔案。我們可以提前對它進行ODex操作,讓它在被DexClassLoader加載的時候,跳過DexOpt的部分工作,進而加快加載的過程.(可參考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進行修改:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  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' // enable multidex  
  9.         // optional  
  10.         // dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() // enable the main-dex-list  
  11.     }  
  12. }  

但是預設的Dalvik 類加載器隻會尋找classes.dex,是以需要将它們進行合并才能使得被識别

當然,現在有了android.support.multidex.jar的支援,一切都會變得非常簡單,首先我們看一下相關源碼的目錄,具體的原理分析我會在之後的文章中進行講解:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  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  

具體內建:

将如下配置加入工程  classPath 中

  [java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. repositories {  
  2.   jcenter()  
  3. }  
  4. dependencies {  
  5.   compile 'com.google.android:multidex:0.1'  
  6. }  

MultiDex實作原理:

        Apk在運作的時候,有一個dexpathlist,而Multidex的源碼中,會根據你的系統版本号對dexpathlist做修改,将所有的dex都添加到dexpathlist中.

接下來內建有兩個步驟:

一. 從sdk\extras\android\support\multidex\library\libs 目錄将android-support-multidex.jar導入工程中

二. 如果你的工程中已經含有Application類,那麼讓它繼承android.support.multidex.MultiDexApplication類,

     如果你的Application已經繼承了其他類并且不想做改動,那麼還有另外一種使用方式,覆寫attachBaseContext()方法:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. public class MyApplication extends FooApplication {  
  2.     @Override  
  3.     protected void attachBaseContext(Context base) {  
  4.         super.attachBaseContext(base);  
  5.         MultiDex.install(this);  
  6.     }  
  7. }  

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

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. android {  
  2.     compileSdkVersion 21  
  3.     buildToolsVersion "21.1.0"  
  4.     defaultConfig {  
  5.         ...  
  6.         minSdkVersion 14  
  7.         targetSdkVersion 21  
  8.         ...  
  9.         // Enabling multidex support.  
  10.         multiDexEnabled true  
  11.     }  
  12.     ...  
  13. }  
  14. dependencies {  
  15.   compile 'com.android.support:multidex:1.0.0'  
  16. }  

使用MutiDex的主意事項

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

Application類中邏輯的注意事項:

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

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. @Override  
  2.     public void onCreate() {  
  3.         super.onCreate();  
  4.         final Context mContext = this;  
  5.         new Runnable() {  
  6.             @Override  
  7.             public void run() {  
  8.                 // put your logic here!  
  9.                 // use the mContext instead of this here  
  10.             }  
  11.         }.run();  
  12.     }  

二. 雖然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 ,你可能會見到如下的錯誤:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. Error:Execution failed for task ':app:dexDebug'.  
  2. > com.android.ide.common.internal.LoggedErrorException: Failed to run command:  
  3.     $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex  
  4.     ...  
  5.   Error Code:  
  6.     2  
  7.   Output:  
  8.     UNEXPECTED TOP-LEVEL EXCEPTION:  
  9.     com.android.dex.DexException: Library dex files are not supported in multi-dex mode  
  10.         at com.android.dx.command.dexer.Main.runMultiDex(Main.java:322)  
  11.         at com.android.dx.command.dexer.Main.run(Main.java:228)  
  12.         at com.android.dx.command.dexer.Main.main(Main.java:199)  
  13.         at com.android.dx.command.Main.main(Main.java:103)  

  對于dex 的--multi-dex 選項設定與預編譯的library工程有沖突,是以如果你的應用中包含引用的lirary工程,需要将預編譯設定為false:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. android {  
  2.     // ...  
  3.     dexOptions {  
  4.         preDexLibraries = false  
  5.     }  
  6. }  

OutOfMemoryError: Java heap space

當運作時如果看到如下錯誤:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. UNEXPECTED TOP-LEVEL ERROR:  
  2. java.lang.OutOfMemoryError: Java heap space  

在 dexOptions 中有一個字段用來增加java堆記憶體大小:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. android {  
  2.     // ...  
  3.     dexOptions {  
  4.         javaMaxHeapSize "2g"  
  5.     }  
  6. }      

使用eclipse的開發人員可以參考Google官方文檔

https://developer.android.com/tools/building/multidex.html#mdex-gradle

需要安裝支援gradle建構的插件,下載下傳位址:

http://dist.springsource.com/release/TOOLS/gradle  (目前可能需要翻牆) 

參考相關資料:

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

補充注意事項:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. com.android.dex.DexException: Multiple dex files define L{package}/BuildConfig;  

如果遇到這個錯誤請進行如下檢查:

1. 主工程與依賴library工程包名是否重複

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

解決方案:

1. 修改library工程包名

2. 删除重複jar包

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

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. dependencies {  
  2.             compile fileTree(dir: 'libs', include: ['*.jar'])  
  3.             compile project(':lib-project-module')  
  4.         } 

補充注意事項:

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. com.android.dex.DexException: Multiple dex files define L{package}/BuildConfig;  

如果遇到這個錯誤請進行如下檢查:

1. 主工程與依賴library工程包名是否重複

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

解決方案:

1. 修改library工程包名

2. 删除重複jar包

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

[java]  view plain copy

Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
Android 解決65535的限制 使用android-support-multidex解決Dex超出方法數的限制問題,讓你的應用不再爆棚
  1. dependencies {  
  2.             compile fileTree(dir: 'libs', include: ['*.jar'])  
  3.             compile project(':lib-project-module')  
  4.         } 

出處: 時之沙:  http://blog.csdn.net/t12x3456