Android 突破 DEX 文件的 64K 方法数限制

随着安卓平台的不断发展与壮大,市场上大而全的应用比比皆是,产品需求的变动累积和UI交互的极致追求,除了 resources 文件的俱增,在 Android Project 中依赖的 Library 和 本身写的 Java 代码也会愈来愈多。这些变化,除了会致使打包出的 APK 文件愈来愈大以外,当项目中java代码包含的方法数(method count)超出一个峰值时,编译过程当中就会出现以下错误:javascript

较早版本的编译系统中,错误内容以下:php

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

而在新版编译系统中,是这样:html

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

尽管在不一样版本的编译系统中显示的错误内容不尽相同,但内容中都提到了一个具体的数字:65536,这个数字也是本文要讲到的核心内容:Android 64K Method Counts Limit 的峰值。详细信息,参考官网用户指南:Configure Apps with Over 64K Methodsjava

Android 64K Method Counts Limit


Android Project 通过编译打包,其中的Java代码(包括Library)转化为DEX格式的字节码文件,这是Android 5.0以前的 Dalvik 虚拟机决定的(5.0以后改成 ART 虚拟机),而且采用 short 类型引用 DEX 文件中的 method,这也为method数量的峰值大小埋下了隐患。short 类型可以表示的最大值是 65536,也就说单个 DEX 文件中最多只有 65536 个 method 可以获得引用,若是代码执行了超出部分的 method 引用,天然会报错,如 methodNotFound 等。1K 等于 1024,65536 恰好是 64K,为了便于称呼和使用,就将这个限制规则统称为 64K 方法数的引用限制。android

为了解决 64K 方法数限制的问题,咱们能够在项目中使用 multidex 配置,当项目中的方法数(包括:Android framework,library 和咱们本身写的代码)超过 64K 时,编译系统会自动编译出多个 DEX 文件。git

Multidex Support


Android 5.0 以前,安卓系统采用的是 Dalvik 虚拟机,采用的是JIT技术(Just-in-time compilation,即时编译,运行时编译DEX字节码文件,这也是之前为何安卓手机用户老是诟病Android系统比iOS系统运行卡顿的缘由),限制每一个APK文件只能包含一个 DEX 文件(即 classes.dex)。为了绕开这个限制,Google给咱们提供了multidex support library 兼容包,帮助咱们实现应用程序加载多个DEX文件,而且这个兼容包做为程序的主DEX文件,管理者其余DEX文件的访问。程序员

注意:因为 Instant Run 机制利用的就是 multidex 原理,当项目中minSdkVersion参数设置为20或者更小,而且运行在 Android 4.4 (API 20) 或更低版本的设备中时,Instant Run将失效。github

Android 5.0以后,安卓系统改用了ART虚拟机(Android RunTime),采用的是OAT技术(Ahead-of-time,预编译,在应用安装的时候扫描应用中的全部DEX文件,并编译成一个.oat格式的文件供安卓设备执行,因此相比Dalvik虚拟机下的应用,安装时间较长)。所以能够理解为,使用ART虚拟机下的安卓系统自动支持APK文件中多个DEX的加载。android-studio

注意:使用Instant Run时,若是项目中的minSdkVersion参数设为21或更高版本,Android Studio编译运行时会自动使应用支持multidex。但Instant Run仅仅做用于debug版本,咱们依然须要给release版本配置multidex来避开64K方法数的限制。微信

Config for Multidex With Gradle


Android Gradle 插件在 Android SDK Build Tools 21.1 及更高版本的编译工具上支持multidex做为编译配置的一部分,因此确保咱们的Android SDK Build Tools tools已经更新至21.1或更高版本,而后再来配置应用的multidex部分。

第一步,修改app/build.grale文件,使项目可以使用multidex:

android {

    compileSdkVersion 21
    buildToolsVersion "21.1.0"

    defaultConfig {
        ...

        // Enabling multidex support.
        multiDexEnabled true
    }
    ...
}

dependencies {
  compile 'com.android.support:multidex:1.0.0'
}复制代码

第二步,修改AndroidManifest.xml文件,引用MultiDexApplication类:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yifeng.mdstudysamples">
    <application ... android:name="android.support.multidex.MultiDexApplication">
        ...
    </application>
</manifest>复制代码

有时候,你可能还须要改变一下 javaMaxHeapSize 的大小:

android {
    dexOptions {
        javaMaxHeapSize "4g"
    }
}复制代码

添加这些配置后,编译工具会构建出一个主 DEX 文件(classes.dex)和其余附属 DEX 文件(classes2.dex,classes3.dex 等,若是须要的话),编译系统会将他们打包到 Apk 文件中。

注意:通常咱们会在项目中自定义一个继承自Application的类,此时就须要重写attachBaseContext()方法,并在该方法里面调用MultiDex.install(this)来支持multidex,可参考:MultiDexApplication

Optimizing Multidex Development Builds


multidex 配置下的应用,编译系统须要通过复杂的 DEX 分割运算,致使增长项目的编译时间,从而影响开发人员的开发效率。咱们可使用 productFlavors 构建开发环境和正式环境的不一样 flavors 来优化 multidex 的长时间编译问题。

对于development flavor,设置 minSdkVersion 值为21,运行在Android 5.0以上版本的设备中,使用 ART-supported 格式生成 multidex 的速度要快得多。对于 release flavor,minSdkVersion 值则设为应用实际支持的版本,编译系统耗费较长的时间来生成适配多设备的multidex APK文件。如:

android {
    productFlavors {
        // Define separate dev and prod product flavors.
        dev {
            // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
            // to pre-dex each module and produce an APK that can be tested on
            // Android Lollipop without time consuming dex merging processes.
            minSdkVersion 21
        }
        prod {
            // The actual minSdkVersion for the application.
            minSdkVersion 14
        }
    }
          ...
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                                 'proguard-rules.pro'
        }
    }
}
dependencies {
  compile 'com.android.support:multidex:1.0.0'
}复制代码

这样,在开发阶段,使用 devDebug 类型的变种 app,取消混淆,支持 multidex,而且运行在 5.0 及以上版本的设备中,可以加快编译过程。有关 flavors 的信息,之前写过一篇文章:Android 利用Gradle实现app的环境分离,更多信息能够参考英文手册:Gradle Plugin User Guide,对应中文版译文:Gradle Android插件用户指南翻译

strings count limit


前面咱们说完单一 dex 文件的方法数限制,事实上,还有一个字符串数量限制。若是项目没有使用 multidex 支持的话,当 strings 超出必定限制,编译过程也会出错:

Dex: Error converting bytecode to dex:
Cause: com.android.dex.DexIndexOverflowException: Cannot merge new index 65868 into a non-jumbo instruction!复制代码

不一样项目编译过程当中报错信息里的具体数字可能不一样。Dex 文件中出现的 string 默认是 4 个字节即 16 位大小的 int 类型的数字引用使用的,即单个 Dex 文件最多只能引用 2^16 个 strings,当你的项目中出现超过这个最大数字的字符串引用,而又没有使用 multidex 支持,编译过程便会出错。

对于这种状况,除了使用 multidex,还有另一种解决方案:jumboMode。这个模式容许单个 Dex 文件支持到 32 为大小的 strings 引用,即 2^32 的引用峰值。使用方法是,在工程 app 模块下 build.gradle 文件的 android 配置下添加:

dexOptions {
        jumboMode true
}复制代码

注意:虽然单个 Dex 文件中 strings 数量限制与 method 数量限制很是类似,可是若是项目方法数超过 64K, 咱们仍是须要使用 multidex 来解决,注意区分。有关这方面的更多详细介绍,请参考 Dalvik bytecode

Methods Count Statistics


尽管安卓系统支持multidex,咱们仍是要学会分析咱们的应用,查看各个部分的方法数,减小冗余方法。这里推荐几个工具,帮助咱们分析。

Library Methods count

一个在线统计 Android Library 方法数的网站,可以统计出 Android 领域常见 libraries 的方法数、JAR 文件和 DEX 文件大小,而且可以选择不一样版本,以图表的形式展现出来。

http://www.methodscount.com/

该网站也提供了Android Studio的插件,帮助咱们分析项目中所依赖的libraries的方法数,如图所示:

methodscount-samples-02.png

methodscount-samples-03.png

Apk Method Count

一个在线统计 APK 文件方法数的开源项目,只须要将须要分析的APK文件拖拽上传至此,便可获得分析结果,如图:

apk-method-count-samples.png

Android Studio APK Analyzer

最后,要重磅推荐Android Studio自带的APK Analyzer,功能齐全,使用方便,绝对是安卓开发人员分析应用的不二选择。使用 Android Studio APK Analyzer ,咱们至少可以作到:

  • 查看APK压缩文件中各个子文件的大小(如DEX和resource文件)

  • 理解DEX文件的结构

  • 快速查看APK文件的版本信息(直接查看AndroidManifest.xml内容)

  • 直观地比较两个APK文件内容

Android-Studio-APK-Analyzer-Samples.png

开发阶段使用Android Studio打开一个项目时,有三种方式使用APK Analyzer工具:

  • 直接拖拽APK文件到Android Studio的编辑窗口

  • 双击打开项目目录app/build/outputs/apk/下的APK文件

  • 点击菜单栏Build->Analyse APK...并选择APK文件

关于我:亦枫,博客地址:yifeng.studio/,新浪微博:IT亦枫

微信扫描二维码,欢迎关注个人我的公众号:安卓笔记侠

不只分享个人原创技术文章,还有程序员的职场遐想

相关文章
相关标签/搜索