以下参考自官方文档配置方法数超过 64K 的应用


随着 Android 平台的持续成长,Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时,您会遇到构建错误,指明您的应用已达到 Android 应用构建架构的极限。早期版本的构建系统按如下方式报告这一错误:

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

较新版本的 Android 构建系统虽然显示的错误不同,但指示的是同一问题:

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

这些错误状况都会显示下面这个数字:65,536。这个数字很重要,因为它代表的是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数。本页介绍如何通过启用被称为 Dalvik 可执行文件分包的应用配置来越过这一限制,使您的应用能够构建并读取 Dalvik 可执行文件分包 DEX 文件。

关于 64K 引用限制

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

Android 5.0 之前版本的 Dalvik 可执行文件分包支持

Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex
字节码文件。要想绕过这一限制,您可以使用 Dalvik 可执行文件分包支持库,它会成为您的应用主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。
注:如果您的项目配置时所面向的 Dalvik 可执行文件分包使用的是 minSdkVersion 20
或更低版本,并且您将其部署到运行 Android 4.4(API 级别 20)或更低版本的目标设备上,则 Android Studio 会停用 Instant Run

Android 5.0 及更高版本的 Dalvik 可执行文件分包支持

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描classesN.dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。
如需了解有关 Android 5.0 运行时的详细信息,请参阅 ART 和 Dalvik
注:如果将应用的 minSdkVersion
设置为 21 或更高值,使用 Instant Run 时,Android Studio 会自动将应用配置为进行 Dalvik 可执行文件分包。由于 Instant Run 仅适用于调试版本的应用,您仍需配置发布构建进行 Dalvik 可执行文件分包,以规避 64K 限制。

规避 64K 限制

在将您的应用配置为支持使用 64K 或更多方法引用之前,您应该采取措施减少应用代码调用的引用总数,包括由您的应用代码或包含的库定义的方法。下列策略可帮助您避免达到 DEX 引用限制:
检查您的应用的直接和传递依赖项 - 确保您在应用中使用任何庞大依赖库所带来的好处大于为应用添加大量代码所带来的弊端。一种常见的反面模式是,仅仅为了使用几个实用方法就在应用中加入非常庞大的库。减少您的应用代码依赖项往往能够帮助您规避 dex 引用限制。
通过 ProGuard 移除未使用的代码 - 为您的版本构建启用代码压缩以运行 ProGuard。启用压缩可确保您交付的 APK 不含有未使用的代码。
使用这些技巧使您不必在应用中启用 Dalvik 可执行文件分包,同时还会减小 APK 的总体大小。

配置您的应用进行 Dalvik 可执行文件分包

将您的应用项目设置为使用 Dalvik 可执行文件分包配置需要对您的应用项目进行以下修改,具体取决于应用支持的最低 Android 版本。
如果您的 minSdkVersion 设置为 21 或更高值,您只需在模块级 build.gradle 文件中将 multiDexEnabled
设置为 true,如此处所示:

android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 25
multiDexEnabled true
}
...
}

但是,如果您的 minSdkVersion 设置为 20 或更低值,则您必须按如下方式使用 Dalvik 可执行文件分包支持库
-修改模块级 build.gradle 文件以启用 Dalvik 可执行文件分包,并将 Dalvik 可执行文件分包库添加为依赖项,如此处所示:
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 25 multiDexEnabled true } ...}dependencies { compile 'com.android.support:multidex:1.0.1'}

  • 根据是否要替换 Application类,执行以下操作之一:如果您没有替换 Application类,请编辑清单文件,按如下方式设置 <application> 标记中的 android:name:

    <?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 类,请按如下方式对其进行更改以扩展 MultiDexApplication(如果可能):
    public class MyApplication extends MultiDexApplication { ... }

或者,如果您替换了 Application 类,但无法更改基本类,则可以改为替换 attachBaseContext()方法并调用 MultiDex.install(this) 来启用 Dalvik 可执行文件分包:

public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
}

构建应用后,Android 构建工具会根据需要构建主 DEX 文件 (classes.dex) 和辅助 DEX 文件(classes2.dex
和 classes3.dex 等)。然后,构建系统会将所有 DEX 文件打包到您的 APK 中。
运行时,Dalvik 可执行文件分包 API 使用特殊的类加载器来搜索适用于您的方法的所有 DEX 文件(而不是仅在主 classes.dex 文件中搜索)。

Dalvik 可执行文件分包支持库的局限性

Dalvik 可执行文件分包支持库具有一些已知的局限性,将其纳入您的应用构建配置之中时,您应该注意这些局限性并进行针对性的测试:

  • 启动期间在设备数据分区中安装 DEX 文件的过程相当复杂,如果辅助 DEX 文件较大,可能会导致应用无响应 (ANR) 错误。在此情况下,您应该通过 ProGuard 应用代码压缩以尽量减小 DEX 文件的大小,并移除未使用的那部分代码。
  • 由于存在 Dalvik linearAlloc 错误(问题 22586),使用 Dalvik 可执行文件分包的应用可能无法在运行的平台版本早于 Android 4.0(API 级别 14)的设备上启动。如果您的目标 API 级别低于 14,请务必针对这些版本的平台进行测试,因为您的应用可能会在启动时或加载特定类群时出现问题。代码压缩可以减少甚至有可能消除这些潜在问题。
  • 由于存在 Dalvik linearAlloc 限制(问题 78035),因此,如果使用 Dalvik 可执行文件分包配置的应用发出非常庞大的内存分配请求,则可能会在运行期间发生崩溃。尽管 Android 4.0(API 级别 14)提高了分配限制,但在 Android 5.0(API 级别 21)之前的 Android 版本上,应用仍有可能遭遇这一限制。

声明主 DEX 文件中需要的类

为 Dalvik 可执行文件分包构建每个 DEX 文件时,构建工具会执行复杂的决策制定来确定主要 DEX 文件中需要的类,以便应用能够成功启动。如果启动期间需要的任何类未在主 DEX 文件中提供,那么您的应用将崩溃并出现错误 java.lang.NoClassDefFoundError。
该情况不应出现在直接从应用代码访问的代码上,因为构建工具能识别这些代码路径,但可能在代码路径可见性较低(如使用的库具有复杂的依赖项)时出现。例如,如果代码使用自检机制或从原生代码调用 Java 方法,那么这些类可能不会被识别为主 DEX 文件中的必需项。
因此,如果您收到 java.lang.NoClassDefFoundError,则必须使用构建类型中的 multiDexKeepFile 或 multiDexKeepProguard 属性声明它们,以手动将这些其他类指定为主 DEX 文件中的必需项。如果类在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配,则该类会添加至主 DEX 文件。

multiDexKeepFile 属性

您在 multiDexKeepFile 中指定的文件应该每行包含一个类,并且采用 com/example/MyClass.class
的格式。例如,您可以创建一个名为 multidex-config.txt 的文件,如下所示:

com/example/MyClass.class
com/example/MyOtherClass.class

然后,您可以按以下方式针对构建类型声明该文件:

android {
buildTypes {
release {
multiDexKeepFile file 'multidex-config.txt'
...
}
}
}

请记住,Gradle 会读取相对于 build.gradle 文件的路径,因此如果 multidex-config.txt 与 build.gradle 文件在同一目录中,以上示例将有效。

multiDexKeepProguard 属性

multiDexKeepProguard 文件使用与 Proguard 相同的格式,并且支持整个 Proguard 语法。如需了解有关 Proguard 格式和语法的详细信息,请参阅 Proguard 手册中的 Keep Options 一节。
您在 multiDexKeepProguard 中指定的文件应该在任何有效的 ProGuard 语法中包含 -keep 选项。例如,-keep com.example.MyClass.class。您可以创建一个名为 multidex-config.pro 的文件,如下所示:

-keep class com.example.MyClass-keep class com.example.MyClassToo

如果您想要指定包中的所有类,文件将如下所示:

-keep class com.example.** { *; } // All classes in the com.example package

然后,您可以按以下方式针对构建类型声明该文件:

android {
buildTypes {
release {
multiDexKeepProguard 'multidex-config.pro'
...
}
}
}

优化开发构建中的 Dalvik 可执行文件分包

Dalvik 可执行文件分包配置会大幅增加构建处理时间,因为构建系统必须就哪些类必须包括在主 DEX 文件中以及哪些类可以包括在辅助 DEX 文件中作出复杂的决策。这意味着使用 Dalvik 可执行文件分包的增量式构建通常耗时更长,可能会拖慢您的开发进度。
为了缩短耗时更长的 Dalvik 可执行文件分包输出构建时间,请利用 productFlavors(一个开发定制和一个发布定制,具有不同的 minSdkVersion 值)创建两个构建变型。
对于开发定制,将 minSdkVersion 设置为 21。该设置将启用一个名为 pre-dexing 的构建功能,此功能使用仅适用于 Android 5.0(API 级别 21)和更高版本的 ART 格式更快生成 Dalvik 可执行文件分包输出。对于发布定制,将 minSdkVersion 设置为适于您的实际最低支持级别。此设置生成的 Dalvik 可执行文件分包 APK 可兼容更多设备,但构建时间更长。
以下构建配置示例展示了如何在 Gradle 构建文件中设置这些定制:

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.1'
}

您完成此配置变更后,可以为增量式构建使用应用的 devDebug 变体,后者集 dev 产品定制与 debug
构建类型的属性于一身。这将创建已启用 Dalvik 可执行文件分包且禁用 proguard 的可调试应用(因为 minifyEnabled 默认为 false)。这些设置会使适用于 Gradle 的 Android 插件执行以下操作:

  1. 执行 pre-dexing:将每个应用模块和每个依赖项构建为单独的 DEX 文件。
  2. 将每个 DEX 文件加入 APK,并且不做任何修改(不执行代码压缩)。
  3. 最重要的是,模块 DEX 文件不执行合并操作,因此可以避免为确定主 DEX 文件的内容而进行长时间的计算。

这些设置的好处是,可以进行快速的增量式构建,因为只有修改过的模块的 DEX 文件才会在后续构建期间重新计算并重新打包。但是,这些构建的 APK 只能用于在 Android 5.0 设备上进行测试。不过,由于是以定制形式实现配置,您保留了使用与发布相适的最低 API 级别和 ProGuard 代码压缩执行正常构建的能力。
您还可以构建其他变体,包括 prodDebug 变体构建,该变体虽然构建时间更长,但可用于开发以外的测试。在所示配置内,prodRelease 变体将是最终测试和发布版本。如需了解有关使用构建变体的详细信息,请参阅配置构建变体

提示:由于您有适用于不同 Dalvik 可执行文件分包需求的不同构建变体,因此也可以为不同变体提供不同清单文件(这样,只有适用于 API 级别 20 和更低版本的清单文件会更改 <application> 标记名称),或者为每个变体创建不同的 Application 子类(这样,只有适用于 API 级别 20 和更低版本的清单文件会扩展 MultiDexApplication类或调用 MultiDex.install(this))。

测试 Dalvik 可执行文件分包应用

编写面向 Dalvik 可执行文件分包应用的仪器测试时,无需进行其他配置。AndroidJUnitRunner
直接支持 Dalvik 可执行文件分包,前提是您使用MultiDexApplication 或替换您的自定义 Application
对象中的 attachBaseContext() 方法,并调用MultiDex.install(this)以启用 Dalvik 可执行文件分包。

或者,您可以替换 AndroidJUnitRunner 中的 onCreate() 方法:

public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}

注:目前不支持使用 Dalvik 可执行文件分包来创建测试 APK。


模拟65K问题

在一个文件中输出65535个方法,拷贝到项目中的一个Class(Methods.java)。
结果报出编译异常:

Error:The number of method references in a .dex file cannot exceed 64K.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html

在module的build.gradle加入配置:

android {
defaultConfig {
// Enabling multidex support.
multiDexEnabled true
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}

此时编译通过了。

统计APK的方法数和字段数

在根目录的build.gradle中配置:

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.3.0'
}
}

在module(默认app目录)下的build.gradle配置:

apply plugin: 'com.getkeepsafe.dexcount'

测试结果

编译时设置最小api14::
minSdkVersion 14
因为未用proguard对未使用的方法优化,build apk后可以统计总的方法和子段数:

Total methods in testapplication-release-unsigned.apk: 82585
Total fields in testapplication-release-unsigned.apk: 7745

可见已超过上限65535。
分析APK包:总计生成了3个dex。

官方文档中提到:

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描classesN.dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。

如果将最小支持的api改为21(Android 5.0):
minSdkVersion 21

则不需要添加:

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

并且在生成的apk包里看到不止一个dex,在我试验的项目一共生成了18个dex。这些dex由Android ART虚拟机原生支持编译成单个oat文件供Android设备执行。

如果你的项目最少支持API14,还是老老实实添加'com.android.support:multidex:1.0.1',并且在Application中执行MultiDex的install方法动态加载多个dex。

Application的静态块和静态变量导致的ClassNotFoundError

参考《深入理解Java虚拟机》,一个类被加载验证后,首先进入准备阶段,即类变量和静态代码块会合并成<clinit>()方法优先于构造方法执行。
如果生成的classes.dex中不包含静态代码块引用的类则必然会报错。
所幸的是在我使用的Android2.2.2环境,显示的调用类或者通过Java反射技术得到类,都会在编译阶段被识别,可以在module的build/intermediates/multi-dex/release(debug)/maindexlist.txt中找到Methods.class类的全限定名。据了解早期的AS未做此优化。
在Native层通过JNIEnv* 的FindClass得到一个类,此时AS未正确将Methods.class编译到主DEX,抛出了ClassNotFoundError。在module的build/intermediates/multi-dex/release(debug)/maindexlist.txt果然也未找到该类。因此还是需要我们参考官方文档的配置方法,将需要的类编译进主DEX中。

至于Android 5.0以上,由于虚拟机的优化,即使不通过MultiDex的install()方法,默认也会加载APK的多个DEX,因此如果你设置的最小适配API是21,就不需要考虑这个问题了。

MultiDex加载多DEX原理

参考MultiDex与热修复实现原理
MultiDex在Application的attachBaseContext()函数阶段通过Java反射技术替换掉默认的BaseDexClassLoaderDexPathList中的Element[]dexElements数组。

APK方法数超过65535及MultiDex解决方案的更多相关文章

  1. Android的方法数超过65535问题

    Under the Hood: Dalvik patch for Facebook for Android 先来看一段中文内容 Hack Dalvik VM解决Android 2.3 DEX/Line ...

  2. Android工程方法数超过65535的解决办法

    Error:Execution failed for task ':ttt:transformClassesWithDexForDebug'.com.android.build.api.transfo ...

  3. Android 方法数超过64k、编译OOM、编译过慢解决方案。

    目前将项目中的leancloud的即时通讯改为环信的即时通讯.当引入easeui的时候 出现方法数超过上限的问题. 搜索一下问题,解决方法很简单. 这里简单记录一下,顺序记录一下此解决方案导致的另一个 ...

  4. (转载)Android 方法数超过64k、编译OOM、编译过慢解决方案。

    Android 方法数超过64k.编译OOM.编译过慢解决方案.   目前将项目中的leancloud的即时通讯改为环信的即时通讯.当引入easeui的时候 出现方法数超过上限的问题. 搜索一下问题, ...

  5. Multidex (方法数超过限制的处理)

    报错 : Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536 ...

  6. Android工程方法数超过64k,The number of method references in a .dex file cannot exceed 64K.

    最近将一个老的Eclipse项目转到Android Studio后,用gradle添加了几个依赖,项目可以make,但是一旦run就报错 Error:The number of method refe ...

  7. Dex文件方法数超过65536怎么破?

    你的应用中的Dex 文件方法数超过了最大值65536的上限,会提示你: UNEXPECTED TOP-LEVEL EXCEPTION:java.lang.IllegalArgumentExceptio ...

  8. 查看APK方法数的工具dex-method-counts

    做APK方法总能遇到方法数超限的问题(主要是方法数, 字段数, String数.等各种数都可能超过65k导致不能安装) 除了大公司都自己做了一些检查方法. 网上还有一些开源的查询工具. 给大家推荐一个 ...

  9. 导出Excel超过65535条限制解决方案

    使用poi导出excel的时候如果数据过多,超过65535条会报错,因为excel2003一个sheet表最多导出65535条,excel2007是10万4000多条限制. 因此遇到这种excel导出 ...

随机推荐

  1. jdk命令行工具:jstat与jmap

    转自文章:http://blog.csdn.net/gzh0222/article/details/8538727 C:\Users\Administrator\Desktop>jstat -g ...

  2. 微信小程序填坑之旅一(接入)

    一.小程序简介 小程序是什么? 首先“程序”这两个字我们不陌生.看看你手机上的各个软件,那就是程序.平时的程序是直接跑在我们原生的操作系统上面的.小程序是间接跑在原生系统上的.因为它嵌入在微信中,受微 ...

  3. MT【175】刚刚凑巧

    已知$\Delta ABC$满足$\sin^2A+\sin^2B+\sin^2C=2\sqrt{3}\sin A\sin B\sin C,a=2$,求$A$ 提示:利用正弦定理:$a^2+b^2+c^ ...

  4. 【BZOJ1296】[SCOI2009]粉刷匠(动态规划)

    [BZOJ1296][SCOI2009]粉刷匠(动态规划) 题面 BZOJ 洛谷 题解 一眼题吧. 对于每个串做一次\(dp\),求出这个串刷若干次次能够达到的最大值,然后背包合并所有的结果即可. # ...

  5. HGOI20180814 (NOIP 模拟Day1)

    100pts=40+60+0 rank 56 若串联那么显然是这样: 若并联那么显然是这样: 串联时C<1,并联时C>1,贪心策略<1时尽可能串联,>1时尽可能并联 考虑这样一 ...

  6. Problem C Dist 解题报告

    Problem C Dist Description 有一个\(n\)个点带边权的连通无向图,边集用\(k\)个集合\(s_1,s_2,\dots,s_k\)和\(k\)个整数\(w_1,w_2,\d ...

  7. 使用alien命令让deb包和rpm包互相转换

    OS version: CentOS7 / Debian9 发现alien这个命令时很惊喜,之前在debian上安装etcd找不到安装包感觉很不科学,有了alien命令事情一下就变简单了. 这里以et ...

  8. python的内置模块re模块方法详解以及使用

    正则表达式 一.普通字符 .     通配符一个.只匹配一个字符 匹配任意除换行符"\n"外的字符(在DOTALL模式中也能匹配换行符 >>> import re ...

  9. python高级特性和高阶函数

    python高级特性 1.集合的推导式 列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if ...

  10. C语言复习---杨辉三角打印

    #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <math ...