1. 程式人生 > >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.<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這個內部類且又是執行時異常,那是不是和ClassLoader有關係呢?
那麼首先排除程式碼原因,開始從Gradle和Gradle外掛版本入手,通過改變版本來驗證。然而驗證下來發現與Gradle並沒關係。

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

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

apk包中含有兩個.dex檔案:classes.dexclasses2.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 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.

如果你的應用中已經繼承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 productFlavorbuildType屬性的devDebug變體app。
這個變體app包含如下特性:

  • 關閉了混淆(proguard)
  • 支援multidex
  • minSdkVersion 設定為 Android API level 21.

這些設定將使Gradle外掛做如下事情:

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

值得注意的是:上述配置後的devDebug變種app僅能執行在Android 5.0裝置上

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

在Android Studio中構建變種App

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

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

  1. 從左邊欄開啟Build Variants視窗

  2. 點選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'
        }
    }

備註:文中連結為官方連結,請爬牆觀看!

原創文章,歡迎轉載,轉載請註明出處
我的簡書賬號是ConnorLin,歡迎光臨!


歡迎關注我的微信