在Android项目中启用ProGuard

最近不得不面对Android 65k method limit问题,所以启用ProGuard来除去一些没有被调用到的方法来降低被引用的方法总数(referenced method count)是一个不错的暂时的解决办法。

启用ProGuard
如何启用其实非常简单。如果项目是用Maven作为build system的,在app项目的pom文件里的android maven插件的节点下加入:

<proguard>
    <skip>false<skip>
    <config>proguard-project.txt</config>
</proguard>

proguard-project.txt即是配置文件,待会儿会详细讲。如果是Gradle的话,可以在buildTypes里加入1

runProguard true
proguardFiles file('proguard-project.txt')

启用后,每次build一般都会生成四份文件,dump.txt/mapping.txt/seeds.txt以及usage.txt。

  • dump.txt:包含了最终在.apk文件里的类结构
  • mapping.txt:如果启用了混淆代码的话(默认是开启的),这个文件里会列出原来的类/方法/成员的名字与混淆后的对应
  • seeds.txt:列出了没有混淆的类及其成员
  • usage.txt:列出了那些被去除的代码

配置ProGuard
在Android SDK的tools/proguard文件夹下有两个默认的配置文件,proguard-android-optimize.txt和proguard-android.txt。从文件名中可以看出一个是启用了优化另一个则是普通的配置文件。按需选择一个对一般的app来讲就足够用了。不过如果是因为64k问题而选择proguard的话,肯定也是个不一般的app,所以需要根据自己app的情况添加一些规则。这里有完整的ProGuard用法。那接下来说下我自己添加的几条规则供参考:

  • Warning: can’t find superclass/interface/referenced class:一般来讲,这是启用proguard最先会遇到的警告。这个警告说的是无法找到父类/借口/被引用的类,出现这个有可能是真的所需要的类没有被找到,需要指定包含这些类的jar的所在位置,但对于Android来说,很多情况下只需要使用-dontwarn规则来关闭这些警告就可以,因为在Android的编译过程中会自动指定所引用的jar。
  • -dontobfuscate:混淆代码后会使debug非常困难。即是是发布的版本,如果使用Crashlytics,最后收到的crash报告也是混淆后的,还是会使debug变得非常困难。虽然可以用retrace脚本来转换混淆后的stack trace,但是这样多做一步并不是很值得。不过如果你的app因为安全原因必须要混淆代码,那也只有这条路可以走了。
  • Automated Instrumentation Testing:如果你的项目里有Instrumentation测试,需要加入-keep public class * extends android.app.Application这样一条规则。因为每个测试项目会自动生成一个android.app.Application类,而ProGuard则认为这些Application类没有在任何地方被调用就自动剔除了,所以测试就没法在ci服务器上运行。这点我当时花了一天时间才最后解决的。:-(
  • Play Services:这个庞大的库本身就占用了大概两三万的方法数,虽然这个库在Android开发中算是必须的,但其实大部分都未必用得到,于是就造成了浪费。Google对于这个库有它自己推荐的规则,直接添加进来就可以。好消息是Play Services下一个版本6.5将会模块化。
  • Reflection:用到reflection的地方也是需要添加规则来保留这些类的。

Android 65k
现在我们来吐槽下这个65k问题Davlik是Android的运行时,其指令里有一个method reference index的变量,就是Davlik里最多能调用的方法数量。Android从出生到现在这个变量的大小从来没有改变过,就是16 bit,也就是令人发指的65536,从此,臭名昭著的65k限制由此而生。除了使用ProGuard外,还有一种比较流行的办法是multi-dex。

Ambitious New Android Developer

Multi-dex
随着app体积越来越大,即使用了ProGuard也还是会导致引用的方法数超过65536,于是有人就想到那我就编译多个dex文件,其中一个是主要的dex文件,然后在运行时再载入其他所需的dex。在Android新的运行时ART发布之前,这是非常繁琐低效的。随着ART的发布,multi-dex有了原生的支持,多个dex文件会在app安装的时候被编译成一个.oat文件让ART来执行,所以也不需要在运行时才载入所需的dex。而在ART之前的版本,Google也发布了支持multidex的库。详细的设置可以看这里:Configuring Your App for Multidex with Gradle。但这个归根到底还是一个hack,而且还是有很多限制,比如不怎么支持Android 4.0之前的版本,极大地增加了编译时间等等。

虽然看上去65k问题的实质很简单,但既然Google这么多年都没有能够修复,或许这真的是一个很难搞的bug。但话又说回来,随着移动领域越发地火热以及迅速地发展,不很好地解决这个问题实在很说不过去。


  1. Gradle指定ProGuard配置文件的时候还有类似getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'这样的写法,直接从SDK里找到默认的配置文件再与自己项目特定的配置文件整合在一起,这点是Android Maven插件还无法做到的。