前段時間在開發(fā)一個RN項目時而钞,我是負責安卓原生端組件的開發(fā)冰更,由于集成了大量的第三方sdk裕膀,項目編譯的時候正常辑鲤,但是運行時卻出現(xiàn)了java.lang.NoClassDefFoundError這個錯誤,并且在不同的安卓版本手機運行表現(xiàn)形式都不一樣喷屋,在安卓版本5.0以下的手機運行都會報這個錯琳拨,在5.0及以上的手機運行就正常,不會報錯屯曹,這就讓我非常郁悶了狱庇。最后查找了大量的網(wǎng)上資料才發(fā)現(xiàn)是安卓打包生成Dex 文件方法數(shù)超過了最大值65536的上限引起的,這個時候打包時應該出現(xiàn)下面這個錯誤:
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)
目前已發(fā)現(xiàn)的兩種隱式報錯
但是項目并沒有預期的出現(xiàn)這個錯誤是牢,實際報錯為java.lang.NoClassDefFoundError僵井,意思就是找不到某個類,報錯信息也沒有看到65536關鍵字(開發(fā)中還發(fā)現(xiàn)另一種隱式報錯java.lang.VerifyError驳棱,也是由于65536上限引起的),這種隱式報錯導致我解決問題的時候走了很多彎路农曲,后來查找大量的資料才發(fā)現(xiàn)安卓Dex文件方法數(shù)超過了65536的上限(這種隱式報錯真讓人很頭疼)社搅。
關于65536限制
在Android系統(tǒng)中,一個App的所有代碼都在一個Dex文件里面乳规。Dex是一個類似Jar的存儲了多有Java編譯字節(jié)碼的歸檔文件形葬。因為android系統(tǒng)使用Dalvik虛擬機,所以需要把使用Java Compiler編譯之后的class文件轉換成Dalvik能夠執(zhí)行的class文件暮的。這里需要強調的是笙以,Dex和Jar一樣是一個歸檔文件,里面仍然是Java代碼對應的字節(jié)碼文件冻辩。當Android系統(tǒng)啟動一個應用的時候猖腕,有一步是對Dex進行優(yōu)化拆祈,這個過程有一個專門的工具來處理,叫DexOpt倘感。DexOpt的執(zhí)行過程是在第一次加載Dex文件的時候執(zhí)行的放坏。這個過程會生成一個ODEX文件,即Optimised Dex老玛。執(zhí)行ODex的效率會比直接執(zhí)行Dex文件的效率要高很多淤年。但是在早期的Android系統(tǒng)中,DexOpt有一個問題蜡豹,也就是這篇文章想要說明并解決的問題麸粮。DexOpt會把每一個類的方法id檢索起來,存在一個鏈表結構里面镜廉。但是這個鏈表的長度是用一個short類型來保存的弄诲,導致了方法id的數(shù)目不能夠超過65536個。當一個項目足夠大的時候桨吊,顯然這個方法數(shù)的上限是不夠的威根,所以最終就會報java.lang.NoClassDefFoundError。
解決方案:
1视乐、修改build.gradle配置:
defaultConfig {
...
multiDexEnabled true
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
2洛搀、根據(jù)是否要替換 Application類,執(zhí)行以下操作之一:
如果您沒有重寫 Application類佑淀,請編輯清單文件留美,按如下方式設置<application>標記中的android:name:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:name="android.support.multidex.MultiDexApplication" >
...
</application>
</manifest>
如果您重寫了 Application類,請按如下方式對其進行更改以繼承MultiDexApplication(如果可能):
public class MyApplication extends MultiDexApplication { ... }
或者伸刃,如果您重寫了 Application類谎砾,但無法繼承基本類,則可以改為重寫 attachBaseContext()方法并調用MultiDex.install(this)
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
}
參考資料:https://developer.android.com/studio/build/multidex.html#mdex-gradle