Android 突破64K方法数的限制

随着安卓平台的不断发展与壮大,市场上大而全的应用比比皆是,产品需求的变更累积和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 的峰值。详细信息,参考官网用户指南:Configure Apps with Over 64K Methods

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 level 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>

添加这些配置后,编译工具会构建出一个主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插件用户指南翻译

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文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容