Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536,其中包括 Android 框架方法、库方法以及您自己代码中的方法。
单个Dex文件中,method个数采用使用原生类型short来索引,即2个字节最多65536个method,field、class的个数也均有此限制。
对于Dex文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是使用Dex工具将class文件转化为Dex文件的过程中, 单个Dex文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536。
这就是65536问题的根本来源。
另外还有一种情况,有时候方法数没有达到 65536,并且编译器也正常的完成了编译工作,但是应用在低版本手机安装是异常终止,异常信息如下:
E/dalvikvm: Optimization failed
E/installd: dexopt failed on '/data/dalvik-cache/data@[email protected]@classes.dex' res = 65433
为什么会出现这种情况呢?其实是这样的,dexopt是一个程序,应用在安装时,系统会通过dexopt 来优化dex文件,在优化的过程中 dexopt 采用一个固定大小的缓冲去来储存应用中的所有方法的信息,这个缓冲区就是 LinearAlloc。LinearAlloc缓冲区在新版本的 Android 系统中其大小时是 8MB 或者 16MB,但是在 Android 2.2和2.3中却只有5MB,当待安装的apk方法数比较多的时候,尽管它没有达到65536的限制,但是它的储存空间仍然有可能超过5MB,这中情况下dexopt程序就会报错。
Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex
字节码文件。想要绕过这个限制,就需要使用Google提供的Dalvik 可执行文件分包支持库。
因为Android系统在启动应用时只加载了主dex(Classes.dex),其他的 dex 需要我们在应用启动后进行动态加载安装。
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 加载其他的dex文件
// 原理就是:通过反射手动添加其他Dex文件中的class到 ClassLoader 的 pathList字段中,就可以实现类的动态加载
MultiDex.install(this);
}
}
这个过程一般只在第一次冷启动应用的时候比较耗时,除了要抽取其他的 dex 文件,Dalvik 虚拟机还会使用 dex2oat 将 dex 文件优化成 odex 文件,将生成的文件放在手机的data/dalvik-cache目录下,便于以后使用。以后再次运行时,因为不用再次生成 odex,所以运行速度很快。
Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描 classesN.dex
文件,并将它们编译成单个 .oat
文件,供 Android 设备执行。因此,如果您的 minSdkVersion
为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。只需要在模块级 build.gradle 文件中将 multiDexEnabled 设置为 true,如此处所示:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 28
multiDexEnabled true
}
...
}
当我们使用了分包支持库之后,在运行app时可能会出现这样的错误
出现这个问题的原因是:在应用启动期间,需要该类,但是这个类不在 MainDex 中,所以解决方案就是将这个类放到 MainDex 中。
具体可以参考官方文档: 声明主 DEX 文件中需要的类 。
dalvik的dexopt程序分配一块内存来统计你的app的dex里面的classes的信息,由于classes太多方法太多超过这个linearAlloc 的限制 。
解决方案就是减少 dex 的大小。
android.applicationVariants.all {
variant ->
dex.doFirst{
dex->
if (dex.additionalParameters == null) {
dex.additionalParameters = []
}
dex.additionalParameters += '--set-max-idx-number=48000'
}
}
启动期间在设备数据分区中安装 DEX 文件的过程相当复杂,如果辅助 DEX 文件较大,可能会导致应用无响应 (ANR) 错误。在此情况下,您应该通过 ProGuard 应用代码压缩以尽量减小 DEX 文件的大小,并移除未使用的那部分代码。
这个是官方给出的建议,但是显然不太合适中国程序员国情,所以需要使用别的方案:
这两个实现其实差不多,都是提供了一种避免在其他Dex文件未加载完成时,造成的ClassNotFoundException的手段。