随着安卓平台的一直倒退与壮大,市场上大而全的利用亘古未有,产品需要的变更累积和UI交互的极致谋求,除了 resources 文件的俱增,在 Android Project 中依赖的 Library 和 本人写的 Java 代码也会越来越多。这些变动,除了会导致打包出的 APK 文件越来越大之外,当我的项目中java代码蕴含的办法数(method count)超出一个峰值时,编译过程中就会呈现如下谬误:

较早版本的编译系统中,谬误内容如下:

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,这个数字也是本文要讲到的核心内容: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 办法数的援用限度。

为了解决 64K 办法数限度的问题,咱们能够在我的项目中应用 multidex 配置,当我的项目中的办法数(包含:Android framework,library 和咱们本人写的代码)超过 64K 时,编译系统会主动编译出多个 DEX 文件。

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将生效。

Android 5.0之后,安卓零碎改用了ART虚拟机(Android RunTime),采纳的是OAT技术(Ahead-of-time,预编译,在利用装置的时候扫描利用中的所有DEX文件,并编译成一个.oat格局的文件供安卓设施执行,所以相比Dalvik虚拟机下的利用,安装时间较长)。因而能够了解为,应用ART虚拟机下的安卓零碎主动反对APK文件中多个DEX的加载。

留神:应用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文件

    相干教程

    Android根底系列教程:
    Android根底课程U-小结_哔哩哔哩_bilibili
    Android根底课程UI-布局_哔哩哔哩_bilibili
    Android根底课程UI-控件_哔哩哔哩_bilibili
    Android根底课程UI-动画_哔哩哔哩_bilibili
    Android根底课程-activity的应用_哔哩哔哩_bilibili
    Android根底课程-Fragment应用办法_哔哩哔哩_bilibili
    Android根底课程-热修复/热更新技术原理_哔哩哔哩_bilibili