目录
- 前言
- 问题
- 解决
- minSdkVersion小于21的配置
- minSdkVersion 大于等于21的配置
- 其它的一些补充
- 参考
前言
我们知道,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.gradle
android {
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.class
- 然后在启用分包的模块
build.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'
}
}
}
- 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#limitationshttps://stackoverflow.com/questions/43666425/android-5-x-classnotfoundexception-works-fine-on-6-0/47890503#47890503?newreg=f4d0f4571e22466495e3ec69a026bfc9