Android應(yīng)用使用Multidex突破64K方法數(shù)限制

寫在前面

前幾天,開發(fā)中遇到一個問題,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.<clinit>(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這個內(nèi)部類且又是運行時異常,那是不是和ClassLoader有關(guān)系呢?
那么首先排除代碼原因,開始從Gradle和Gradle插件版本入手,通過改變版本來驗證。然而驗證下來發(fā)現(xiàn)與Gradle并沒關(guān)系。

那么問題到底出在哪呢?
沒轍!于是開始按節(jié)點排查,排查過幾個關(guān)鍵節(jié)點后,終于得出一個結(jié)論:引入某個特定library后就會報這個錯。

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

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

接下來,我們借助官方文檔來了解下64K方法數(shù)限制。

正文

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

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.

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

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

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

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

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

Android5.0及更高版本支持Multidex

Android5.0及更高版本使用支持從apk中加載多個dex文件的ART運行時機制,在應(yīng)用安裝時,加載classed(...N).dex文件并編譯成一個.oat文件以支持在Android設(shè)備上運行。關(guān)于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之前,你或許可以通過以下方法來減小方法總數(shù)(包括引用的、library里的和自己寫的方法)。

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

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

使用Gradle配置Multidex

Android SDK Build Tools 21.1或更高版本上支持multidex,確定要配置multidex前請確保Android SDK Build ToolsAndroid 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的引用,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.multidex.myapplication">
    <application
        ...
        android:name="android.support.multidex.MultiDexApplication">
        ...
    </application>
</manifest>

通過以上步驟即可支持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.

如果你的應(yīng)用中已經(jīng)繼承Application,那么可以通過復(fù)寫attachBaseContext()方法并調(diào)用MultiDex.install(this)來支持multidex,即無需修改manifest文件。更多信息請看MultiDexApplication

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

multidex support library的使用限制

multidex support library有一些已知的限制請務(wù)必知曉,需要在應(yīng)用時先行測試。

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

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

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

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

優(yōu)化Multidex的開發(fā)和構(gòu)建

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

開發(fā)時將minSdkVersion改為21使用ART運行時機制,這樣能加快構(gòu)建速度。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'
}

完成上述配置后,你可以使用結(jié)合了dev productFlavorbuildType屬性的devDebug變體app。
這個變體app包含如下特性:

  • 關(guān)閉了混淆(proguard)
  • 支持multidex
  • minSdkVersion 設(shè)置為 Android API level 21.

這些設(shè)置將使Gradle插件做如下事情:

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

值得注意的是:上述配置后的devDebug變種app僅能運行在Android 5.0設(shè)備上

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

在Android Studio中構(gòu)建變種App

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

在Android Studio中構(gòu)建變體app,步驟如下:

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

測試Multidex應(yīng)用

測試multidex應(yīng)用,需在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'
    }
}

備注:文中鏈接為官方鏈接,請爬墻觀看!

參考資料
1.Building Apps with Over 64K Methods

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 隨著安卓平臺的不斷發(fā)展與壯大,市場上大而全的應(yīng)用比比皆是,產(chǎn)品需求的變更累積和UI交互的極致追求,除了 resou...
    亦楓閱讀 3,359評論 5 45
  • 這一章主要針對項目中可以用到的一些實用功能來介紹Android Gradle,比如如何隱藏我們的證書文件,降低風(fēng)險...
    acc8226閱讀 7,960評論 3 25
  • 隨著安卓平臺的不斷發(fā)展與壯大,市場上大而全的應(yīng)用比比皆是,產(chǎn)品需求的變更累積和UI交互的極致追求,除了 resou...
    阿俊貳閱讀 899評論 0 14
  • 梧桐葉又落 在還未儲藏夠過冬的糧草 八月的喪鐘就已敲響 生命如此黯淡 一場山的傾倒就可碾壓 一陣風(fēng)的放肆就會墜落 ...
    footprint123閱讀 266評論 0 1

友情鏈接更多精彩內(nèi)容