0%

综合技术(二):使用 multidex 来解决方法数越界

1. 65536 和 64 K 引用限制

  • 当应用及其引用的库包含的方法数超过 65536 时,构建报错:

    1
    2
    3
    4
    5
    6
    7
    8
    // 高版本
    trouble writing output:
    Too many field references: 131000; max is 65536.
    You may try using --multi-dex option.

    // 低版本
    Conversion to Dalvik format failed:
    Unable to execute dex: method ID not in [0, 0xffff]: 65536
  • Android 应用(APK)文件包含 Dalvik Executable(DEX) 文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码

  • Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536,其中包括 Android Framework 方法库方法以及应用程序中所有的方法

  • 在计算机领域,术语“千”表示 1024(即 2^10),由于 65536 = 64 * 1024,因此这一限制称为“64 K 引用限制”

2. 针对 Multidex 配置应用

  • Google 在 2014 年提出了 Multidex 的解决方案,通过 Multidex 可以很好地解决方法数越界的问题

  • 如果 minSdkVersion 设为 21 或更高版本,系统会默认启用 Multidex,并且不需要 Multidex 库

    • Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时环境,它本身支持从 APK 文件加载多个 DEX 文件
    • ART 在应用安装时执行预编译,这会扫描查找 classesN.dex 文件,并将它们编译成单个 .oat 文件,以供 Android 设备执行
  • 如果 minSdkVersion 设为 20 或更低版本,必须使用 Multidex 库并在代码中做相应配置

    1. gradle 文件配置:修改模块级 build.gradle 文件以启用 Multidex,并将 Multidex 库添加为依赖项

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      android {
      defaultConfig {
      ...
      minSdkVersion 15
      targetSdkVersion 28
      // enable multidex support
      multiDexEnabled true
      }
      ...
      }

      dependencies {
      // 老版本
      compile 'com.android.support:multidex:1.0.0'

      // 新版本
      implementation "androidx.multidex:multidex:2.0.1"
      }
    2. 代码中配置:执行以下三种之一即可

      • 修改 manifest 清单文件的 <application> 标签的 android:name 属性

        1
        2
        3
        4
        5
        6
        7
        <application
        // 老版本
        android:name="android.support.multidex.MultiDexApplication"

        // 新版本
        android:name="androidx.multidex.MultiDexApplication" >
        </application>
      • 让应用的 Application 继承 MultiDexApplication

        1
        2
        3
        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);
        }
        }
        • 在 Application 中,attachBaseContext()onCreate() 先执行
        • MultiDex.install() 完成之前,不要通过反射或 JNI 执行 MultiDex.install() 或其他任何代码。MultiDex 跟踪功能不会追溯这些调用,从而导致出现 ClassNotFoundException,或因 DEX 文件之间的类分区错误而导致验证错误
    3. 构建时,Android 构建工具会根据需要构造主 DEX 文件(classes.dex)和辅 DEX 文件(classes2.dex 和 classes3.dex 等)。然后构建系统会将所有 DEX 文件打包到 APK 中

    4. 运行时,MultiDex API 会使用特殊的类加载器搜索适用程序中方法的所有的 DEX 文件(而不是只在主 classes.dex 文件中搜索)

3. Multidex 的局限性

  • 启动期间在设备的数据分区上安装 DEX 文件的过程相当复杂,如果辅 DEX 文件较大,可能会导致 ANR。为避免此问题,应启用代码缩减以尽量减小 DEX 文件的大小,并移除未使用的代码部分
  • 当搭载的版本低于 Android 5.0(API 21)时,使用 Multidex 不足以避开 linearalloc 限制问题 78035)。此上限在 Android 4.0(API 14)中有所提高,但这并未完全解决该问题。在低于 Android 4.0 的版本中,可能会在达到 DEX 索引限制之前达到 linearalloc 限制。因此,如果 target API 低于 14,应该在这些版本的平台上进行全面测试,因为应用可能会在启动时或加载特定类组时出现问题。代码缩减可以减少甚至有可能消除这些问题

4. 声明主 DEX 文件中必需的类

  • 如果主 DEX 文件中未提供启动期间需要的任何类(可能因为使用的库具有复杂的依赖项导致代码路径的可见性较低),则应用会崩溃并报错:java.lang.NoClassDefFoundError
  • 为了避免该报错,可以使用 multiDexKeepFilemultiDexKeepProguard 属性声明这些其他类,以手动方式将这些类指定为主 DEX 文件中的必需类。如果在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配了某个类,则会将该类添加到主 DEX 文件中
-------------------- 本文结束感谢您的阅读 --------------------