Android方法引用數超過65535解決

 

隨着應用不斷迭代更新,業務線的擴展,應用越來越大(比如:集成了各種第三方SDK或者公共開源的Library文件、jar文件)這樣一來,項目耦合性就很高,重複作用的類就越來越多了,SO:問題就來了。
相信大家在做自己公司項目時,都有機會遇到下面的錯誤:

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文件方法數超過了65535的上限,簡單的說,應用爆炸了。
那麼讓我們看一下爲什麼會引起這種錯誤:
在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插件,其他基本上相同):
首先在app/build.gradle文件中配置,如下:

 android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"

    defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 24
        ...

        // 設置支持multidex
        multiDexEnabled true
    }
    ...
 }

 dependencies {
    compile 'com.android.support:multidex:1.0.1'
 }

接下來的配置,有兩種配置方式:
(1) : 在application的子類中配置(如果你需要有Application子類的情況)

package com.example;

import android.app.Application;
import android.content.Context;

/**
 * 
 */
public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

(2) : 如果你不需要application的子類的情況,那就直接集成MultiDexApplication

package com.example;

import android.support.multidex.MultiDexApplication;
import android.content.Context;

/**
 * Extended MultiDexApplication 
 */
public class MyApplication extends MultiDexApplication {

     // 不需要重寫attachBaseContext()

     //..........
}

好了,經過上面兩步的解決,Android 65535的問題,也得到了解決。

使用MutiDex的注意事項:
一、如果你繼承了MutiDexApplication或者覆寫了Application中的attachBaseContext()方法。
Application類中的邏輯的注意事項:
Application中的靜態全局變量會比MutiDex的instal()方法優先加載,所以建議避免在Application類中使用靜態變量引用main classes.dex文件以外dex文件中的類,可以根據如下所示的方式進行修改:

@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();  
    }

二. 雖然Google解決了應用總方法數限制的問題,但並不意味着開發者可以任意擴大項目規模。Multidex仍有一些限制:

  • DEX文件安裝到設備的過程非常複雜,如果第二個DEX文件太大,可能導致應用無響應。此時應該使用ProGuard減小DEX文件的大小。
  • 由於Dalvik linearAlloc的Bug,應用可能無法在Android 4.0之前的版本啓動,如果你的應用要支持這些版本就要多執行測試。
  • 同樣因爲Dalvik linearAlloc的限制,如果請求大量內存可能導致崩潰。Dalvik linearAlloc是一個固定大小的緩衝區。在應用的安裝過程中,系統會運行一個名爲dexopt的程序爲該應用在當前機型中運行做準備。dexopt使用LinearAlloc來存儲應用的方法信息。Android 2.2和2.3的緩衝區只有5MB,Android 4.x提高到了8MB或16MB。當方法數量過多導致超出緩衝區大小時,會造成dexopt崩潰。
  • 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的實現則可以很好地解決此問題。

常見問題
問題1:DexException: Library dex files are not supported in multi-dex mode,你可能會見到如下的錯誤:

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:

android {  
    // ...  
    dexOptions {  
        preDexLibraries = false  
    }  
}

問題2:OutOfMemoryError: Java heap space.當運行時如果看到如下錯誤:

UNEXPECTED TOP-LEVEL ERROR:  
java.lang.OutOfMemoryError: Java heap space

解決方法:在dexOptions中有一個字段用來增加java堆內存大小:

android {  
    // ...  
    dexOptions {  
        javaMaxHeapSize "2g"  
    }  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章