Android 的 classLoader
在加载 APK 的时候限制了 class.dex
包含的 Java 方法数,其总数不能超过65535(64K, 不要再说成 65K
了,1K = 2^10 = 1024 , 64 * 1024 = 65535),Google 官方给出的解决方案是使用 Multidex
。
基本要求:
配置步骤:
修改 module
下的 build.gradle
文件,添加支持库并开启 multidex:
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
PS: compileSdkVersion、buildToolsVersion 根据实际项目配置,但是版本不能低于上述版本。
在 AndroidManifest.xml
中给 application
节点添加对 MultiDexApplication
类的引用:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
PS: manifest
节点的 package
属性值根据实际项目有所不同。
注意:如果你的 APP 使用了继承 Application
的类,你需要重写 attachBaseContext()
方法并调用 MultiDex.install(this)
来启用 multidex 。
public class XXX extends Application{
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
网上搜到还有一个方法:不继承 Application
,而是直接继承 MultiDexApplication
即可,这样就不需要重写 attachBaseContext()
方法了。
参考资料: Configure Apps with Over 64K Methods
Android SDK Build Tools 21.1 或者更高版本中的 Gradle Android 插件有对 multidex 的支持。这个插件使用 Proguard 来分析你的项目并在 [buildDir]/intermediates/multi-dex/[buildType]/maindexlist.txt
文件中生成一个 app 启动 classes 的列表。但是这个列表并不是100%准确,可能会丢失一些app启动所需的 classes 。
如果你在本地的测试机上没有遇到这个问题,并不代表你的 APP 没有问题,我通过查看友盟的崩溃记录和使用一些真机测试平台来进行检查,通常情况下会有所发现。
解决方法:在 module
下创建 multidex.keep
文件,并在其中罗列出那些 class,以便让编译器知道在 main dex 文件中要保持哪些 class。
生成 multidex.keep
文件中的内容有多种:
方法一:修改 module
下的 build.gradle
文件
apply plugin: 'com.android.application'
android {
...
}
dependencies {
...
}
android.applicationVariants.all { variant ->
task "fix${variant.name.capitalize()}MainDexClassList" << {
logger.info "Fixing main dex keep file for $variant.name"
File keepFile = new File("$buildDir/intermediates/multi-dex/$variant.buildType.name/maindexlist.txt")
keepFile.withWriterAppend { w ->
// Get a reader for the input file
w.append('/n')
new File("${projectDir}/multidex.keep").withReader { r ->
// And write data from the input into the output
w << r << '/n'
}
logger.info "Updated main dex keep file for ${keepFile.getAbsolutePath()}/n$keepFile.text"
}
}
}
tasks.whenTaskAdded { task ->
android.applicationVariants.all { variant ->
if (task.name == "create${variant.name.capitalize()}MainDexClassList") {
task.finalizedBy "fix${variant.name.capitalize()}MainDexClassList"
}
}
}
方法二:修改 module
下的 build.gradle
文件
apply plugin: 'com.android.application'
android {
...
afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex' // enable multidex
// optional
dx.additionalParameters += "--main-dex-list=$projectDir/class-list.txt".toString() // enable the main-dex-list
dx.additionalParameters += '--minimal-main-dex'
}
}
}
dependencies {
...
}
使用上述任意方式配置完成后, clean
然后 rebuild
项目,完成之后在 module
下的 build/intermediates/multi-dex/xxx
里找到 maindexlist.txt
文件(如果找不到相关目录,可能需要你同步后 rebuild
项目才能生成),复制里面的内容到 module
根目录下 multidex.keep
文件中(没有则先创建此文件)。
然后,比较重要的一步就是:通过友盟、测试记录、Bug记录等获取到 NoClassDefFoundError
错误对应的类,按照 maindexlist.txt
文件的方式添加这些类到 multidex.keep
文件中就可解决了。
比如 首次安装启动时黑屏没有响应/ANR 、 安装时异常 等,你可以参考文末的一些文章,此外你还可以参考 Android 必知必会-Android Splash 页秒开之细节处理 来优化启动体验。
参考资料和推荐阅读:
这是一篇早就准备写的文章,但当时搜集的资料未及时保存或者丢失,就拖到了现在。因为一个比较旧的 APP 也遇到了相关的问题,所以重新搜集了下资料整理发布出来了,希望能帮到遇到相关问题的朋友们。
PS:你可以通过下面的方式和我联系