给App启用MultiDex功能

转自:http://kaedea.com/2015/09/02/android/enable-multidex/

App 启动 MultiDex 主要是为了解决 “65535 方法数超标” 以及 “INSTALL_FAILED_DEXOPT” 问题,就目前来说,对于使用 Android Studio 的朋友来说,MultiDex 这个术语应该不陌生。而对于那些从早期使用 Eclipse 开发 Android 的人来说,这个词语则更加再熟悉不过了,因为用 Eclipse 开启 MultiDex 功能实在是太坑爹了(默默给 Eclipse 加一把土)。

出现的问题

65535 问题

当 App 的功能越来越丰富、使用的库越来越多时,其包含的 Java 方法总数也越来越多,这时候就会出现 65535 问题。

在构建 apk 的时候限制了一个 dex 文件能包含的方法数,其总数不能超过 65535(则 64K,1K = 2^10 = 1024 , 64 * 1024 = 65535)。MultiDex, 顾名思义,是指多 dex 实现,大多数 App,解压其 apk 后,一般只有一个 classes.dex 文件,采用 MultiDex 的 App 解压后可以看到有 classes.dex,classes2.dex,… classes(N).dex,这样每个 dex 都可以最大承载 64k 个方法,很大限度地缓解了单 dex 方法数限制。

(2016-10-1 具体原因分析可以参考由 Android-65K 方法数限制引发的思考)

LinearAlloc 问题

现在这个问题已经不常见了,它多发生在 2.x 版本的设备上,安装时会提示 INSTALL_FAILED_DEXOPT。这个问题发生在安装期间,在使用 Dalvik 虚拟机的设备上安装 APK 时,会通过 DexOpt 工具将 Dex 文件优化为 odex 文件,即 Optimized Dex,这样可以提高执行效率 (不同的设备需要不同的 odex 格式,所以这个过程只能安装 apk 后进行)。

LinearAlloc 是一个固定大小的缓冲区,dexopt 使用 LinearAlloc 来存储应用的方法信息,在 Android 的不同版本中有 4M/5M/8M/16M 等不同大小,目前主流 4.x 系统上都已到 8MB 或 16MB,但是在 Gingerbread 或以下系统(2.2 和 2.3)LinearAlloc 分配空间只有 5M 大小的。当应用的方法信息过多导致超出缓冲区大小时,会造成 dexopt 崩溃,造成 INSTALL_FAILED_DEXOPT 错误。

启用 MultiDex 解决问题

直接忽视 Eclipse,在 Android Studio 中,可以通过以下方式来启用 MultiDex。

环境要求

  • 使用 Android Studio 开发工具;
  • Android SDK Build Tools >= 21.1;
  • 使用 Support Repository 中的 multidex (刚出来时是 0.1 版本,目前已有有 1.0.0 和 1.0.1);

配置 build.gradle

 
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
      
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
multiDexEnabled true // Enable MultiDex.
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}

在代码里启动 MultiDex

最后,在 Java 代码里启动 MultiDex,有两种方式可以搞定。

方式一,使用 MultiDexApplication。

 
      
1
2
3
4
5
6
7
8
9
 
      
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package= "xxx">
<application
...
android:name= "android.support.multidex.MultiDexApplication">
...
application>
manifest>

方式二,在自己的 Application#attachBaseContext(Context) 方法里添加以下代码。

 
      
1
2
3
4
5
6
 
      
public class MyApplication extends Application {
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install( this); // Enable MultiDex.
}
}

存在的问题

MultiDex 并不是万全的方案,Google 貌似不太热衷于旧版本的兼容工作,通过阅读 MultiDex Support 库的源码,我们也能发现其代码写得貌似没有那么严谨。

目前来说,使用 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 文件又可以安装了,这个问题现在基本没有出现了,不再追究原因了。

参考资料:

  • Configure Apps with Over 64K Methods
  • Android 使用 android-support-multidex 解决 Dex 超出方法数的限制问题, 让你的应用不再爆棚

你可能感兴趣的:(android)