前言
安卓应用大小随安卓平台持续成长在增加。应用及其引用库达特定大小遇构建错误,指明应用已达安卓应用构建架构极限。早版构建系统报错如下:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
新版构建系统虽提示不同,但指问题相同:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
这些错误状况显示数65536。该数很重要,代表单Dalvik Executable(DEX)字节码文件内代码可调用引用总数。本文介绍如何通过启用被称为Dalvik可执行文件分包的应用配置越过该限制,使应用构建并读取Dalvik可执行文件分包DEX文件。
关于64K引用限制
安卓应用(APK)文件包含Dalvik Executable(DEX)文件形式可执行字节码文件,其中包含运行应用已编译代码。Dalvik Executable规范使在单DEX文件内可引用方法总数限制在65536,其中包括安卓框架方法、库方法及代码方法。计算机科学领域内术语千(简称 K)表1024(2^10)。65,536 等64 X 1024,该限制亦称“64K引用限制”。
安卓5.0前版Dalvik可执行文件分包支持
安卓5.0(API 21)前版用Dalvik运行时执行应用代码。默认情况Dalvik限制应用每APK用单classes.dex字节码文件。绕过该限制用Dalvik可执行文件分包支持库,它成应用主DEX文件一部分,然后管理对其它DEX文件及其所包含代码的访问。
注:项目配置面向Dalvik可执行文件分包所用minSdkVersion 20或更低且将其部署到安卓4.4(API 20)或更低目标设备则Android Studio停用Instant Run。
安卓5.0及更高版Dalvik可执行文件分包支持
安卓5.0(API 21)及更高版用名为ART的运行时,后者原生支持从APK文件加载多DEX文件。ART在应用安装时执行预编译,扫描classesN.dex文件并将其编译成单.oat文件供安卓设备执行。故minSdkVersion为21或更高则不需Dalvik可执行文件分包支持库。
了解安卓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可执行文件分包配置需应用项目进行如下修改,具体取决于应用所支持最低安卓版本。
minSdkVersion设21或更高只需在模块级build.gradle中multiDexEnabled设true:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 26
multiDexEnabled true
}
...
}
minSdkVersion设20或更低须按如下方式用Dalvik可执行文件分包支持库:
- 修改模块级build.gradle以启用Dalvik可执行文件分包并将Dalvik可执行文件分包库添加为依赖项:
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 26 multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.1' }
- 据是否替换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); } }
-
构建应用后安卓构建工具据需构建主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)前应用仍可能遭遇该限制。
声明主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插件执行以下操作:
- 执行pre-dexing,将每应用模块和每依赖项构建为单独DEX文件。
- 将每DEX文件加入APK且不做任何修改(不执行代码压缩)。
- 最重要,模块DEX文件不执行合并操作,因此可避免为确定主DEX文件内容而进行长时间计算。
这些设置好处是,可进行快速增量式构建,因仅修改过模块DEX文件才会在后续构建期重计算并重打包。但这些构建的APK仅用于在Android 5.0设备测试。不过因以定制形式实现配置,故保留了使用与发布相适的最低API级别和ProGuard代码压缩执行正常构建能力。
还可构建其它变体,包括prodDebug变体构建,该变体虽构建时间更长,但可用于开发以外测试。在所示配置内,prodRelease变体将是最终测试和发布版本。了解有关使用构建变体的详细信息参阅配置构建变体。
提示:因有适用于不同Dalvik可执行文件分包需求的不同构建变体,因此可为不同变体提供不同清单文件(这样仅适用于API级别20和更低版本清单文件会更改标记名称)或为每变体创建不同的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。