Android 使用android-support-multidex解決Dex超出方法數的限制問題,

隨着應用不斷迭代,業務線的擴展,應用愈來愈大(好比集成了各類第三方sdk或者公共支持的jar包,項目耦合性高,重複做用的類愈來愈多),相信不少人都遇到過以下的錯誤:
 html

?java

1
2
3
4
5
6
7
8
9
10
11
12
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

那麼讓咱們看一下爲何會引發這種錯誤:git

 

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有一個問題,也就是這篇文章想要說明並解決的問題。DexOpt會把每個類的方法id檢索起來,存在一個鏈表結構裏面。可是這個鏈表的長度是用一個short類型來保存的,致使了方法id的數目不可以超過65536個。當一個項目足夠大的時候,顯然這個方法數的上限是不夠的。儘管在新版本的Android系統中,DexOpt修復了這個問題,可是咱們仍然須要對低版本的Android系統作兼容.github

目前比較經常使用的方法:(1) 應用插件化,好比使用我正在參與開發的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,若是有建議或者相關的問題,歡迎到Github上積極參與. (2) 分割Dex,多工程: 把所須要的.class文件或者是Jar文件和一些源碼一塊兒編譯生成一個Jar文件。而後使用Android SDK提供的dx工具把Jar文件轉成Dex文件。咱們能夠提早對它進行ODex操做,讓它在被DexClassLoader加載的時候,跳過DexOpt的部分工做,從而加快加載的過程. 這兩種方法並不衝突,插件化除了解決應用爆棚,還有不少其餘的優勢,能夠看我以前的文章,再也不復述.app

固然,Google看來也意識到了目前應用方法數爆棚的問題, 目前在已經在API 21中提供了通用的解決方案,那就是android-support-multidex.jar. 這個jar包最低能夠支持到API 4的版本(Android L及以上版本會默認支持mutidex).框架

讓咱們看一下如何應用android-support-multidex.jar(如下都以在Anroid studio中的使用爲例):ide

 

首先能夠用--multi-dex配置(build.gradle)進行解決,生成的Apk中將包含多個dex文件,好比classes.dex, classes2.dex. 以下,須要對build.gradle進行修改:
工具

?gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
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
     }
}</filename>




可是默認的Dalvik 類加載器只會尋找classes.dex,因此須要將它們進行合併才能使得被識別

固然,如今有了android.support.multidex.jar的支持,一切都會變得很是簡單,首先咱們看一下相關源碼的目錄,具體的原理分析我會在以後的文章中進行講解:

?

1
2
3
4
5
6
7
8
9
10
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


具體集成:
將以下配置加入工程 classPath

?

1
2
3
4
5
6
7
repositories {
   jcenter()
}
 
dependencies {
   compile 'com.google.android:multidex:0.1'
}




接下來集成有兩個步驟:

一. 從sdkextrasandroidsupportmultidexlibrarylibs 目錄將android-support-multidex.jar導入工程中

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

若是你的Application已經繼承了其餘類而且不想作改動,那麼還有另一種使用方式,覆寫attachBaseContext()方法:



?

1
2
3
4
5
6
7
public class MyApplication extends FooApplication {
     @Override
     protected void attachBaseContext(Context base) {
         super .attachBaseContext(base);
         MultiDex.install( this );
     }
}



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

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
buildscript {
     repositories {
         jcenter()
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:0.13.3'
         classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.1'
     }
}
 
apply plugin: 'com.android.application'
apply plugin: 'jp.leafytree.android-scala'
 
repositories {
     jcenter()
}
 
android {
     compileSdkVersion 21
     buildToolsVersion '21.0.2'
 
     defaultConfig {
         applicationId 'some.app'
         minSdkVersion 19
         targetSdkVersion 19
         versionCode 1
         versionName '1.0'
     }
}
 
dependencies {
     compile 'com.google.android:multidex:0.1'
     compile 'com.android.support:support-v4:19.0.1'
     compile 'com.google.android.gms:play-services:5.0.77'
     compile 'org.scala-lang:scala-library:2.11.2'
     compile 'org.scaloid:scaloid_2.11:3.4-10'
}




使用MutiDex的主意事項

 

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

Application類中邏輯的注意事項:

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

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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();
     }



常見問題

 

DexException: Library dex files are not supported in multi-dex mode,你可能會見到以下的錯誤:

 

 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
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 選項設置與預編譯的library工程有衝突,所以若是你的應用中包含引用的lirary工程,須要將預編譯設置爲false:

?

1
2
3
4
5
6
android {
     // ...
     dexOptions {
         preDexLibraries = false
     }
}
相關文章
相關標籤/搜索