前段时间在开发一个RN项目时,我是负责安卓原生端组件的开发,由于集成了大量的第三方sdk,项目编译的时候正常,但是运行时却出现了java.lang.NoClassDefFoundError这个错误,并且在不同的安卓版本手机运行表现形式都不一样,在安卓版本5.0以下的手机运行都会报这个错,在5.0及以上的手机运行就正常,不会报错,这就让我非常郁闷了。最后查找了大量的网上资料才发现是安卓打包生成Dex 文件方法数超过了最大值65536的上限引起的,这个时候打包时应该出现下面这个错误:
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)
目前已发现的两种隐式报错
但是项目并没有预期的出现这个错误,实际报错为java.lang.NoClassDefFoundError,意思就是找不到某个类,报错信息也没有看到65536关键字(开发中还发现另一种隐式报错java.lang.VerifyError,也是由于65536上限引起的),这种隐式报错导致我解决问题的时候走了很多弯路,后来查找大量的资料才发现安卓Dex文件方法数超过了65536的上限(这种隐式报错真让人很头疼)。
关于65536限制
在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个。当一个项目足够大的时候,显然这个方法数的上限是不够的,所以最终就会报java.lang.NoClassDefFoundError。
解决方案:
1、修改build.gradle配置:
defaultConfig {
...
multiDexEnabled true
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
2、根据是否要替换 Application类,执行以下操作之一:
如果您没有重写 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
网友评论