Android應用使用Multidex突破64K方法數限制

Android應用使用Multidex突破64K方法數限制。

前幾天,開發中遇到一個問題,Log信息如下:

E/AndroidRuntime(10943): FATAL EXCEPTION: main
E/AndroidRuntime(10943): Process: com.freeme.gallery, PID: 10943
E/AndroidRuntime(10943): java.lang.NoClassDefFoundError: com.freeme.gallery.data.DataManager$DateTakenComparator
E/AndroidRuntime(10943):     at com.freeme.gallery.data.DataManager.(DataManager.java:65)
E/AndroidRuntime(10943):     at com.freeme.gallery.app.GalleryAppImpl.getDataManager(GalleryAppImpl.java:77)
E/AndroidRuntime(10943):     at com.freeme.gallery.provider.GalleryProvider.onCreate(GalleryProvider.java:101)
E/AndroidRuntime(10943):     at android.content.ContentProvider.attachInfo(ContentProvider.java:1656)
E/AndroidRuntime(10943):     at android.content.ContentProvider.attachInfo(ContentProvider.java:1627)
E/AndroidRuntime(10943):     at android.app.ActivityThread.installProvider(ActivityThread.java:5060)
E/AndroidRuntime(10943):     at android.app.ActivityThread.installContentProviders(ActivityThread.java:4634)
E/AndroidRuntime(10943):     at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4567)
E/AndroidRuntime(10943):     at android.app.ActivityThread.access$1500(ActivityThread.java:153)
E/AndroidRuntime(10943):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1404)
E/AndroidRuntime(10943):     at android.os.Handler.dispatchMessage(Handler.java:110)
E/AndroidRuntime(10943):     at android.os.Looper.loop(Looper.java:193)
E/AndroidRuntime(10943):     at android.app.ActivityThread.main(ActivityThread.java:5351)
E/AndroidRuntime(10943):     at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(10943):     at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(10943):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:835)
E/AndroidRuntime(10943):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:651)
E/AndroidRuntime(10943):     at dalvik.system.NativeStart.main(Native Method)

從報錯信息來看,是沒有找到DateTakenComparator這個內部類且又是運行時異常,那是不是和ClassLoader有關系呢?
那麼首先排除代碼原因,開始從Gradle和Gradle插件版本入手,通過改變版本來驗證。然而驗證下來發現與Gradle並沒關系。

那麼問題到底出在哪呢?
沒轍!於是開始按節點排查,排查過幾個關鍵節點後,終於得出一個結論:引入某個特定library後就會報這個錯

然而這個library是直接從Maven導入的,library本身肯定沒有問題。似乎到這裡線索又斷瞭...恰逢此時,同事建議看下apk包大小。不看不知道,看過才恍然大悟,apk內大有乾坤啊。

apk包中含有兩個.dex文件:classes.dex和classes2.dex,再看java.lang.NoClassDefFoundError,結果顯而易見,方法數超限瞭!但是已經在build.gradle中配置瞭multiDexEnabled true和添加瞭android.support.multidex,為何還會出錯呢? 原來是忘瞭繼承MultiDexApplication瞭!敲腦袋ing...

接下來,我們借助官方文檔來瞭解下64K方法數限制。

正文

隨著應用不斷增加新功能,引入新庫,apk會越來越大,到達一定規模後就可能遇到方法數超限問題。
早期版本錯誤信息如下:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

較新版本錯誤信息如下:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

其中數字65536是關鍵,Android平臺的Java虛擬機Dalvik執行Dex程序時,使用的是short類型來索引DEX文件中的方法。這就意味著單個Dex文件可被引用的方法總數被限制為64x1024, 即65536。其中包括:

Android Framework的方法library的方法我們自己寫的方法

為突破這個限制,需要使用multidex來生成多個dex文件。

Android5.0 (API level 21)之前版本支持Multidex

Android5.0之前使用Dalvik運行時執行應用代碼,默認Dalvik限制每個apk隻能有一個字節碼classed.dex文件。為突破這個限制,可以使用multidex support library來管理額外的dex文件(包括代碼)。

Android5.0及更高版本支持Multidex

Android5.0及更高版本使用支持從apk中加載多個dex文件的ART運行時機制,在應用安裝時,加載classed(...N).dex文件並編譯成一個.oat文件以支持在Android設備上運行。關於Android 5.0運行時詳見ART介紹。

Note: While using Instant Run, Android Studio automatically configures your app for multidex when your app's minSdkVersion is set to 21 or higher. Because Instant Run only works with the debug version of your app, you still need to configure your release build for multidex to avoid the 64K limit.

如果使用Instant Run,當app的minSdkVersion大於或等於21時,Android Studio會自動配置支持multidex,但是僅debug版本有效,release版仍然需要配置multidex來突破64K限制。

避免64K限制

在配置multidex之前,你或許可以通過以下方法來減小方法總數(包括引用的、library裡的和自己寫的方法)。

排除未使用的依賴 -此步驟通常能有效避免64K限制。使用ProGuard去除未使用的方法 -為release版本配置ProGuard,能有效排除一些無用方法

使用以上技術能有效避免更改構建配置來引用更多的方法,同時能減小apk大小,使用戶消耗更少的流量。

使用Gradle配置Multidex

Android SDK Build Tools 21.1或更高版本上支持multidex,確定要配置multidex前請確保Android SDK Build Tools和Android Support Repository更新到較新版本。

通過以下步驟配置multidex:

更改Gradle配置來支持multidex修改manifest。使其支持multidexapplication類

修改模塊級builde.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'
}

在manifest文件中,添加MultidexApplication Class的引用,如下:



    
        ...
    

通過以上步驟即可支持multidex。

Note: If your app uses extends the Application class, you can override the attachBaseContext() method and call MultiDex.install(this) to enable multidex. For more information, see the MultiDexApplication reference documentation.

如果你的應用中已經繼承Application,那麼可以通過復寫attachBaseContext()方法並調用MultiDex.install(this)來支持multidex,即無需修改manifest文件。更多信息請看MultiDexApplication

補充:
亦可直接將繼承Application 改為繼承MultiDexApplication,而無需修改manifest文件或復寫attachBaseContext()方法。

multidex support library的使用限制

multidex support library有一些已知的限制請務必知曉,需要在應用時先行測試。

如果classes2.dex文件較大,安裝dex文件到設備的數據區是一個復雜的過程,可能會導致應用程序無響應(ANR)的錯誤。在這種情況下,應該使用ProGuard盡量減小dex文件的大小且刪除無用的代碼。

在Android 4.0(API Level 14)之前,由於Dalvik linearalloc bug(問題22586),multidex可能是運行失敗。如果希望運行在Level 14之前的Android系統版本,請先確保完整的測試和使用。優化代碼可以減少或可能消除這些潛在的問題。

應用程序使用瞭multiedex配置,會造成申請很大的內存分配。可能還會引起Dalvik虛擬機的崩潰(問題78035)。此分配限制是在Android 4.0 (API level 14)上增加的,但Android5.0 (API level 21)之前的版本仍有此限制。

multidex構建工具不支持指定哪些類必須包含在首個dex文件中,因而可能導致某些library無法使用。

優化Multidex的開發和構建

multidex會加長構建應用的時間,這個必要的過程可能會拖慢你的開發進度。
為加速構建過程,我們可以在Gradle中配置productFlavors: a development flavor and a production flavor.

開發時將minSdkVersion改為21使用ART運行時機制,這樣能加快構建速度。release時改為合適的minSdkVersion,這樣僅在release時費時較長。

build.gradle配置如下:

android {
    productFlavors {
        // Define separate dev and prod product flavors.
        dev {
            // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
            // to pre-dex each module and produce an APK that can be tested on
            // Android Lollipop without time consuming dex merging processes.
            minSdkVersion 21
        }
        prod {
            // The actual minSdkVersion for the application.
            minSdkVersion 14
        }
    }
          ...
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                                 'proguard-rules.pro'
        }
    }
}
dependencies {
  compile 'com.android.support:multidex:1.0.0'
}

完成上述配置後,你可以使用結合瞭dev productFlavor和buildType屬性的devDebug變體app。
這個變體app包含如下特性:

關閉瞭混淆(proguard)支持multidexminSdkVersion 設置為 Android API level 21.

這些設置將使Gradle插件做如下事情:

編譯應用的每個模塊(包括依賴)為獨立的dex文件,這個過程稱為pre-dexing不作修改地include每個dex文件到apk裡更重要的是,這些模塊dex文件將不會合並,這樣避免分割主dex文件,以加快速度

值得註意的是:上述配置後的devDebug變種app僅能運行在Android 5.0設備上

同時,你也可以構建其他變體app,也可以在終端使用gradel命令來實現多渠道打包等。更多有關flavors和Gradle tasks信息, 請看Gradle Plugin User Guide(中文翻譯).

在Android Studio中構建變種App

使用multidex時,構建變體app對管理構建過程是非常有用的。Android studio允許用戶自己選擇。

在Android Studio中構建變體app,步驟如下:

從左邊欄打開Build Variants窗口點擊build variant以選擇不同變體,如圖:

測試Multidex應用

測試multidex應用,需在build.gradle中配置MultiDexTestRunner:

android {
  defaultConfig {
      ...
      testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
  }
}

Note: With Android Plugin for Gradle versions lower than 1.1, you need to add the following dependency for multidex-instrumentation:

若Gradle插件版本低於1.1,你還需添加multidex-instrumentation依賴:

dependencies {
    androidTestCompile('com.android.support:multidex-instrumentation:1.0.1') {
         exclude group: 'com.android.support', module: 'multidex'
    }
}

You May Also Like