PermaLink Android's Horrid 64K DEX Method Limit08/17/2014
One of the worst design decisions in the Android Dalvik virtual machine is the 64K method limit (this limit includes all methods in your application and all libraries you link in; note that this is not the same as Facebook's 64K Dalvik Runtime issue.  The cryptic compile time error you'll get during a build of your project is:
  java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536

This problem will manifest easily if you hook in a few libraries that have a lot of methods, including notably the Google Play Services Library  which can suck up 1/3 of this limit.  Other libraries that are huge include the Amazon Web Services library and Guava and Protobufs (which you can substitute with Square's Wire to reduce the method count).  You can analyze the method count using dex-method-counts.

We hit this with Mustbin's app a third of the way into development and I had to solve this issue so we could continue. I ended up trying the Proguard approach first because the custom class loader approach seemed a bit like doing binary overlays from ages past and using jarjar to reduce the size of external libraries meant we couldn't just include the libraries through Maven Central.

The Proguard approach is relatively straightforward and lets you debug your application normally, though it does increase your compile time which is bad enough with Android Studio and Gradle (they don't background compile like Eclipse did so it takes nearly a minute for each compile cycle). You don't know if you're took out a method by mistake until runtime when you'll get an error that method is not found. When you see the error in Logcat, you have to add a -keep rule so that it's not removed by Proguard; you can use the app\build\outputs\proguard\<flavor>dump.txt output to help you with the syntax to the keep rule needed to add it back. You also have to enable this in your build.gradle file for your debug builds.

Here is the proguard-rules.txt file that Mustbin uses minus Mustbin-specific keep rules. Mustbin uses Crashlytics, AWS, GreenRobot, Robospice, Retrofit, Butterknife, Spongy Castle, and the metadata-extractor EXIF library so the rules for these are show below (if you have rules for other libraries, feel free to reply to this and add them so others can use them):

#this disables obfuscation so we're just using this to trim unused methods/classes
-dontobfuscate
#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable
-dontoptimize

# Most of these options are from
# http://www.crashlytics.com/blog/mastering-proguard-for-building-lightweight-android-code/
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

-printseeds seeds.txt
-printusage unused.txt
-printmapping mapping.txt

-libraryjars libs

# public Android APISs
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.content.Context
-keep public class * extends android.support.v4.content.LocalBroadcastManager
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.billing.InAppBillingService
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.content.Context {
public void *(android.view.View);
public void *(android.view.MenuItem);
}

# Crashlytics
-keep class com.crashlytics.** { *; }
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }

# Butterknife
-dontwarn butterknife.internal.**
-keep class **$$ViewInjector { *; }
-keepnames class * { @butterknife.InjectView *;}

# Amazon AWS SDK
-keep class org.apache.commons.logging.** { *; }
-keep class com.amazonaws.services.sqs.QueueUrlHandler { *; }
-keep class com.amazonaws.javax.xml.transform.sax.* { public *; }
-keep class com.amazonaws.javax.xml.stream.** { *; }
-keep class com.amazonaws.services.**.model.*Exception* { *; }
-keep class org.codehaus.** { *; }
-keepattributes Signature,*Annotation*
-dontwarn javax.xml.stream.events.**
-dontwarn org.codehaus.jackson.**
-dontwarn org.apache.commons.logging.impl.**
-dontwarn org.apache.http.conn.scheme.**
-keep class com.amazonaws.internal.config.** { *; }

# Jackson library
-keepattributes *Annotation*,EnclosingMethod
-keep public class mydatapackage.** {
public void set*(***);
public *** get*();
}
-keepattributes Signature
-keepnames class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.**

# GreenRobot's event methods
-keepclassmembers class ** {
public void onEvent*(**);
}

# SpongyCastle
-keep class org.spongycastle.**

# Robospice
# For RoboSpice
#Results classes that only extend a generic should be preserved as they will be pruned by Proguard
#as they are "empty", others are kept
-keep class com.mustbin.mustbin.api.**
#RoboSpice requests should be preserved in most cases
-keepclassmembers class com.mustbin.mustbin.api.** {
public void set*(***);
public *** get*();
public *** is*();
}
### Jackson SERIALIZER SETTINGS
-keepclassmembers,allowobfuscation class * {
@org.codehaus.jackson.annotate.* <fields>;
@org.codehaus.jackson.annotate.* <init>(...);
}
## Gson SERIALIZER SETTINGS
# See https://code.google.com/p/google-gson/source/browse/trunk/examples/android-proguard-example/proguard.cfg
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# Gson specific classes
#-keep class sun.misc.Unsafe { *; }

#Retrofit
-keep class com.google.gson.** { *; }
-keep class com.google.inject.** { *; }
-keep class org.apache.http.** { *; }
-keep class org.apache.james.mime4j.** { *; }
-keep class javax.inject.** { *; }
-keep class retrofit.** { *; }

#EXIF library
-keep class com.adobe.xmp.** { *; }
-keep class com.adobe.xmp.XMPMetaFactory { *; }
-keep class com.drew.** { *; }

# supporess gazillions of warnings last
-dontwarn org.apache.**
-dontwarn com.google.**
-dontwarn javax.**
-dontwarn com.adobe.**
-dontwarn org.ietf.**
-dontwarn com.amazonaws.**
-dontwarn rx.**
-dontwarn com.squareup.**
-dontwarn org.w3c.**
-dontwarn org.codehause.**
-dontwarn retrofit.**
-dontwarn java.beans.**

Comments :v

1. ken11/26/2014 11:40:07 AM


Optimizations are turned off so you can still debug the app w/o it jumping randomly when you step through code because Proguard decided to rewrite your code to be more "optimal" (and to obfuscate it)...it's pretty standard practice..




2. Jon11/20/2014 05:26:39 PM


I notice that most people who recommend ProGuard as a tool to help with the 64k method limit use -dontoptimize and -dontobfuscate.
The -dontoptimize seems counterintuitive for this purpose, but everyone seems to use it.
Can you shed any light on what this means relative to the method limit problem and why we're turning off optimizations for that?




Start Pages
RSS News Feed RSS Comments Feed CoComment Integrated
The BlogRoll
Calendar
No calendar found.
Search
Contact Me
About Ken
Full-stack developer (consultant) working with .Net, Java, Android, Javascript (jQuery, Meteor.js, AngularJS), Lotus Domino