前言
我们知道,java代码在执行时首先会由JDK编译成.class字节码文件,然后由JVM运行该程序时会读取字节码文件并同步将其翻译成Native code交由机器执行。
Android源代码到运行app的过程大致也是如此:先由一系列工具将源代码编译成dex字节码文件,构建apk,然后交由android运行时去解释执行。
在android5.0以前,android系统使用的是Dalvik运行时,它解释执行时采用的是JIT(Just-In-Time)方式,也就是app在启动时开始翻译字节码。Android5.0及以后,Android运行时换成了ART,它采用AOT(Ahead-Of-Time)方式翻译字节码,就是在安装apk时将dex文件提前翻译成Native code保存到.oat文件中,运行app时直接读取执行。
通常情况下,每个apk中只会编译成一个dex文件,其中包含了android框架方法,引用库的方法,以及自己写的方法。然而单个dex文件中最多包含65536个方法,简称64K限制。
问题
随着项目规模的扩大,源程序中的方法数最终会超过64k限制,此时如果不进行处理则可能报如下错误:
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.
解决
- 简化项目依赖,避免引入庞大的库。
- 移除项目中无用的代码,使用ProGuard对代码进行压缩。
- 启动dex分包配置,将方法分到多个dex包中。
这里我们说下dex分包的配置:
minSdkVersion小于21的配置
在Android5.0以前使用Dalvik运行时执行应用代码,默认情况下Dalvik运行时只能使用单个dex文件。要绕过此限制则需引入Dalvik可执行文件分包支持库。它会根据需要构建一个主classes.dex文件与多个辅助classesN.dex文件,主dex文件保障app能够正常启动,然后app启动后会使用特殊的类加载器在所有的dex文件中搜索方法。
配置方式:
- 修改模块级别
build.gradleandroid { defaultConfig { multiDexEnabled true //启用分包 } } dependencies { //添加dex分包支持库 compile 'com.android.support:multidex:1.0.3' } - 配置
Application:- 如果没有替换
Application,需要编辑AndroidManifest.xml文件<?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,将替换的Application的基类改成MultiDexApplication。public class MyApplication extends MultiDexApplication { ... } - 若替换了
Application但是又无法替换基类。则覆写attachBaseContext方法并调用MutilDex.install(this)。public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
- 如果没有替换
minSdkVersion 大于等于21的配置
Android5.0以后,系统采用ART运行时,原生支持加载多个dex文件,它会在应用安装时进行预编译,扫描所有的classesN.dex文件,并将其编译成单个.oat文件。
配置如下:
- 只需在模块级的
build.gradle文件中启用dex分包即可。android { defaultConfig { ... minSdkVersion 21 //<<大于21 multiDexEnabled true //启用分包 } }
其它的一些补充
-
一般来说,超过
64k限制往往是我们代码写的太冗余,直接依赖或者传递依赖太多……我们应该尽量规避64k限制而不是无脑启用dex分包:- 简化依赖,避免引入庞大的库
- 通过ProGuard移除未使用代码
-
Dalvik可执行分包支持库存在一些局限:
- 构建dex文件时会执行复杂决策判断类放置到哪个dex文件中,因而会增加项目构建的时间。
- 启动app时安装dex文件的过程相当复杂,如果辅助dex文件太大则很可能会引起ANR。
- Android4.0以前的设备可能无法运行。
- Android5.0之前配置分包的应用如果发出庞大的内存申请,则可能在运行时崩溃。
-
某些情况下主dex文件中可能未自动提供app启动时所需的类(通常是因为某些类的引用路径太过隐晦,构建时自动决策没有正确判断将其添加至主dex文件),这将会在启动时抛出
java.lang.NoClassDefFoundError的错误,此时需要使用构建类型的multiDexKeepFile或multiDexKeepProguard属性来声明这些类,以手动将其添加至主dex文件,以保证app正常启动。- multiDexKeepFile
首先创建一个文件multidex-config.txt用于保存需要添加的类:
然后在启用分包的模块com/example/MyClass.class com/example/MyOtherClass.classbuild.gradle中声明multiDexKeepFile属性:android { buildTypes { release { multiDexKeepFile file 'multidex-config.txt' } } } - multiDexKeepProguard
首先需要创建一个multiDexKeepProguard文件multidex-config.pro,语法与Proguard一样,向其中添加需要keep的类:
然后在启用分包的模块-keep class com.example.MyClass -keep class com.example.MyClassToo -keep class com.example.** { *; }build.gradle中声明multiDexKeepProguard属性:android { buildTypes { release { multiDexKeepProguard 'multidex-config.pro' } } }
- multiDexKeepFile
-
ART中对分包构建的优化:
当
minSdkVersion为21及以上时,会启用一个pre-dexing的构建功能,它将会为每个应用模块和每个依赖构建单独的dex文件,然后将这些dex文件直接加入apk,且不执行合并操作,因而可以节省相当的构建时间。
如果项目最低支持版本不能低于21,则可以使用productFlavors创建开发定制构建变体(Build Variants)与发布构建变体,在不同的变体中指定不同的minSdkVersion:android { defaultConfig { ... multiDexEnabled true } productFlavors { dev { // Enable pre-dexing to produce an APK that can be tested on // Android 5.0+ without the time-consuming DEX build processes. minSdkVersion 21 } prod { // The actual minSdkVersion for the production version. minSdkVersion 14 } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile 'com.android.support:multidex:1.0.3' }这样,在开发测试时我们使用
dev构建变体,利用ART的pre-dexing功能加快构建速度。发布正式版时,使用release构建变体,保留对低版本平台的支持。 -
上面提到ART的
pre-dexing功能可以加快构建速度,然而利用这个功能构建的app在Android5.x系统上运行时可能会崩溃,并且抛出类似以下错误:java.lang.RuntimeException: Unable to instantiate application com.package.TestApplication: java.lang.ClassNotFoundException: Didn't find class "com.package.Application" on path: DexPathList[[zip file "/data/app/com.package.testapp-1/base.apk"],nativeLibraryDirectories=[/data/app/com.package.testapp-1/lib/x86_64, /vendor/lib64, /system/lib64]]这是因为在android5.x系统的ART中,有段代码限制了读取dex分包的数量为100,而
Application或者其它某些类刚好不在前100个dex分包中,导致运行时抛出了XXX未找到的异常。一个简单的解决办法是关闭默认启用的pre-dexing构建功能:android { ... dexOptions { preDexLibraries = false } }这当然会使ART对分包构建的优化失效,所以更好的解决办法是简化项目的依赖,调整项目结构。
参考
https://developer.android.com/studio/build/multidex#limitations
https://stackoverflow.com/questions/43666425/android-5-x-classnotfoundexception-works-fine-on-6-0/47890503#47890503?newreg=f4d0f4571e22466495e3ec69a026bfc9
本文探讨了Android应用中遇到的64K方法限制问题,包括其原因、影响及解决方案,详细介绍了dex分包配置方法,适用于不同版本的Android系统。
1373

被折叠的 条评论
为什么被折叠?



