[TOC]

文章参考:https://blog.csdn.net/luoshengyang/article/details/8744683

文章参考:https://blog.csdn.net/huxin1875/article/details/87816465

文章参考:https://www.kaedea.com/2015/09/02/android/enable-multidex/

概述

我们知道,Android进行发布的时候,都要进行编译成APK。那么Android Studio 按下编译按钮后发生了什么?

我们下面大概总结一下:

  1. 打包资源文件,生成R.java文件(使用工具AAPT)
  2. 处理AIDL文件,生成java代码(没有AIDL则忽略)
  3. 编译 java 文件,生成对应.class文件(java compiler)
  4. .class 文件转换成dex文件(dex)
  5. 打包成没有签名的apk(使用工具apkBuilder)
  6. 使用签名工具给apk签名(使用工具JarSigner)
  7. 对签名后的.apk文件进行对齐处理,不进行对齐处理不能发布到Google Market(使用工具zipAlign)

下面我们就详细的根据自己浅显的理解来讲解一下这几个步骤:

.class 文件转换成dex文件(dex)

多Dex打包

文章参考:https://developer.android.com/studio/build/multidex?hl=zh-cn

将class文件转换成dex文件,默认只会生成一个dex文件,当您的应用及其引用的库包含的方法数超过 65536 时,您会遇到一个构建错误,指明您的应用已达到 Android 构建架构规定的引用限制:

1
2
3
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

较低版本的构建系统会报告一个不同的错误,但指示的是同一问题:

1
2
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

这两种错误情况会显示一个共同的数字:65536。此数字是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数。这就是著名的65535问题。

怎么解决呢?

关于 64K 引用限制

Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536,其中包括 Android 框架方法、库方法以及您自己的代码中的方法。在计算机科学领域内,术语千(简称 K)表示 1024(即 2^10)。由于 65536 等于 64 X 1024,因此这一限制称为“64K 引用限制”。

Android 5.0 之前版本的 MultiDex 支持

Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时执行应用代码。默认情况下,Dalvik 将应用限制为每个 APK 只能使用一个 classes.dex 字节码文件。为了绕过这一限制,您可以向模块级 build.gradle 文件中添加 MultiDex 库:

1
2
3
4
dependencies {
def multidex_version = "2.0.1"
implementation "androidx.multidex:multidex:$multidex_version"
}

如需查看此库的当前版本,请参阅版本页面中有关 MultiDex 的信息。

如果您使用的不是 AndroidX,请改为添加以下已弃用的支持库依赖项:

1
2
3
dependencies {
implementation 'com.android.support:multidex:1.0.3'
}

此库会成为应用的主 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。如需了解详情,请参阅下面有关如何针对 MultiDex 配置应用的部分。

Android 5.0 及更高版本的 MultiDex 支持

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,它本身支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,这会扫描查找 classesN.dex 文件,并将它们编译成单个 .oat 文件,以供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高版本,系统会默认启用 MultiDex,并且您不需要 MultiDex 库。

如需详细了解 Android 5.0 运行时,请阅读 ART 和 Dalvik

规避 64K 限制

在将您的应用配置为支持使用 64K 或更多方法引用之前,您应该采取措施以减少应用代码调用的引用总数,包括由您的应用代码或包含的库定义的方法。以下策略可帮助您避免达到 DEX 引用限制:

  • 检查应用的直接依赖项和传递依赖项 - 确保您在应用中使用任何庞大依赖库所带来的好处多于为应用添加大量代码所带来的弊端。一种常见的反面模式是,仅仅为了使用几个实用方法就在应用中加入非常庞大的库。减少应用代码依赖项往往能够帮助您规避 DEX 引用限制。
  • 通过 R8 移除未使用的代码 - 启用代码缩减以针对发布 build 运行 R8。启用缩减可确保您交付的 APK 不含有未使用的代码。

使用这些技巧使您不必在应用中启用 MultiDex,同时还会减小 APK 的总体大小。

针对 MultiDex 配置应用

因此,如果您的 minSdkVersion 设为 21 或更高版本,系统会默认启用 MultiDex,并且您不需要 MultiDex 库。

不过,如果您的 minSdkVersion 设为 20 或更低版本,您必须使用 MultiDex 库并对应用项目进行以下修改:

修改模块级 build.gradle 文件以启用 MultiDex,并将 MultiDex 库添加为依赖项,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 28
multiDexEnabled true
}
...
}

dependencies {
implementation "androidx.multidex:multidex:2.0.1"
}

根据您是否替换 Application 类,执行以下某项操作:

如果您不替换 Application 类,请修改清单文件以设置 <application> 标记中的 android:name,如下所示:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:name="androidx.multidex.MultiDexApplication" >
...
</application>
</manifest>

如果您替换 Application 类,请对其进行更改以扩展 MultiDexApplication(如果可能),如下所示:

1
public class MyApplication extends MultiDexApplication { ... }

或者,如果您替换 Application 类,但无法更改基类,则可以改为替换 attachBaseContext() 方法并调用 MultiDex.install(this) 以启用 MultiDex:

1
2
3
4
5
6
7
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}

存在的问题

目前来说,使用 MultiDex 可能存在以下问题。

NoClassDefFoundError

如果你在调用 MultiDex#install(Context) 做了别的工作,而这些工作需要用到的类却存在于别的 dex 文件里面(Secondary Dexes),就会出现类找不到的运行时异常。

正确的做法是把这些需要用到的类标记在 multidex.keep 清单文件里面,再在 build.gradle 里面启用该清单文件。

1
2
3
4
5
6
7
8
9
10
11
android {

defaultConfig {
multiDexEnabled true
multiDexKeepProguard file('multidex.pro')
}
}

dependencies {
compile'com.android.support:multidex:1.0.1'
}

multiDexKeepProguard使用的是类似于混淆文件的过滤规则,出了这个配置项之外还有multiDexKeepFile,这个要求你在清单文件里把所有的类都罗列出来。

卡顿问题

通过阅读源码,我们可以看出,目前 Android 5.0 以上的设备已经自身支持了 MultiDex 功能,也就是说在安装 apk 的时候,系统已经会帮我们把 apk 里面的所有 dex 文件都做好 Optimize 处理,所以不需要我们在代码里启用 MultiDex 了。但是对于 Android 5.0 以下的设置,则依然要求我们启用 MultiDex。而这些系统的设备在第一次运行 App 的时候,需要对所有的 Secondary Dexes 文件都进行一次解压以及 Optimize 处理(生成 odex 文件),这段时间会有明显的耗时(大概 5000 毫秒左右,dex 越多越大越明显),所有会产生明显的卡顿现象。

不过好在,在 Application#attachBaseContext(Context) 中,UI 线程的阻塞是不会引发 ANR 的,只不过这段长时间的卡顿(白屏)还是会影响用户体验,解决方案能想到的有两种。

第一种,在安装一个新的 apk 的时候,先在 Worker 线程里做好 MultiDex 的 Optimize 工作,安装 apk 并启动后,直接使用之前 Optimize 产生的 odex 文件,这样就可以避免第一次启动时候的 Optimize 工作。

第二种,启动 App 的时候,先显示一个简单的 Splash 闪屏界面,然后启动 Worker 线程执行 MultiDex#install(Context) 工作,就可以避免 UI 线程阻塞。不过要确保启动以及启动 MultiDex#install(Context) 所需要的类都在 Main Dex 里面。

多进程同步

参考 Google 的 MultiDex 的源码,可以发现 Google 并没有考虑多进程同步的问题,如果 App 是多进程的,并且刚好同时有两个进程出发了 MultiDex#install(Context) 工作,可以会有多进程冲突的风险。

如果你采用了上面解决卡顿问题时说到的第二种方法,你也应该考虑多进程的问题,因为这种方法并不是在进程一启动就在主线程里面去做 MultiDex 初始化了,可能存在同步问题的风险。

其他问题

早期在使用 MultiDex 的时候,Android Studio 会出现 apk 编译后无法安装的问题,然而使用命令行编译出来的 apk 文件又可以安装了,这个问题现在基本没有出现了,不再追究原因了。