1

Since I am using many dependencies in my app, I am reaching the 65k Method Limit (I am reaching 76k methods). I've read on android.developer that proguard is used to shrink the code.

So - does proguard only shrink my application code or does it shrink the code of my dependencies too? Do I need to be wary of something when shrinking code with proguard? How do I do that?

My Gradle Build:

apply plugin: 'com.android.application'

android {
compileSdkVersion 21
buildToolsVersion "21.1.2"

defaultConfig {
    applicationId "some.Path"
    minSdkVersion 15
    targetSdkVersion 21
    versionCode 1
    versionName "1.0"
}

packagingOptions {
    exclude 'META-INF/DEPENDENCIES'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/LICENSE.txt'
}

buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    debug {
        debuggable true
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
}

configurations {
compile.exclude group:  'org.apache.xmlbeans'
}

repositories {
maven { url "https://jitpack.io" }
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.github.PhilJay:MPAndroidChart:v2.1.0'
compile 'com.opencsv:opencsv:3.4'
compile 'org.apache.poi:poi:3.12'
compile 'org.apache.poi:poi-ooxml:3.12'
}
Mosha Pasumansky
  • 13,206
  • 5
  • 32
  • 55
pebbles
  • 356
  • 1
  • 3
  • 19

6 Answers6

14

TL; DR: invert your -keep option unless you love troubles


Firstly: I believe, that you are making right choice by using Proguard to overcome the dex limitation. I would not recommend using multidex support library under any circumstances: it introduces problem of multiple classloaders in your application, and that may backfire in many non-obvious ways.

Here is my personal approach to shrinking the app efficiently:

  • Pick a couple of hugest third-party dependencies you have;
  • Check if those really support Proguard;
  • If they do, shrink them with Proguard;
  • If you still don't fit in maximum method count, do steps above for some of remaining dependencies;
  • If you still don't fit, possibly reevaluate some, that do not support Proguard, possibly read their source code to get better idea why they don't, and apply Proguard to them yourself;
  • In the worst case, apply Proguard to your own code;
  • If absolutely nothing of above helps, use multidex.

Picking dependencies for shrinking

In your case, there aren't many (direct) dependencies in the first place. You may want to look at output of gradlew dependencies to get better idea of your indirect dependencies, some of which may be biggest contributors to total app size. Then you may proceed to use some of tools listed in "Dex" section of Android Arsenal to learn which libraries contribute most to dex method count. You seem to already have a general idea of it, so I won't dwell much on this part.

Remember: shrinking executable code is somewhat non-trivial intervention in library internals, so you'd rather shrink less to avoid mysterious problems in future. If in doubt, start from libraries, that openly declare, that they do support Proguard officially (in your case that would be Android Support libraries).

Note, that "supporting Proguard" may mean different things for different developers. You can expect Android Support Library developers to be at least basically competent, but many others will ship with consumer Proguard rules like this:

-keep class com.example.library.** { *; }

In case you wonder, the above config is based upon many real-life configs, such as Square's Leak Canary Proguard configuration. It does not say anything about overall competency of developers in question, just reminder that using Proguard can be hard. And yes, this kind of configuration will completely prevent shrinking and obfuscation of the library, unless you build it's local copy from source code and remove such helpful consumer-proguard-rules.pro from there.

Evaluating dependencies for Proguard

As shown above, even experienced developers sometimes choose to ignore Proguard. If Google searches regarding the library and it's compatibility with Proguard return nothing (and even if they do return some results!) you may have to make your own judgement regarding usage of Proguard. Here is how I personally do:

  • If there are words "framework", "enterprise", "reflection" anywhere on the library site, it is likely to be poorly compatible with Proguard;
  • If the library has anything to do with compile-time code generation (a-la Butterknife, Dagger etc.), think twice before using Proguard;
  • If the library messes with JNI, think a couple more times before using Proguard on it, and Google for it's effects on Proguard even if you don't shrink the library itself;
  • If in doubt, Google for it and/or read library source code: usage of Class.forName as well as Proxy.getInvocationHandler and similar reflection code are usual bad signs.

Libraries, that offer Android UI components (such as MPAndroidChart) are usually ok to shrink, at least if you keep getDefaultProguardFile('proguard-android.txt') in your Gradle config.

The most important part

A lot of developers (including Proguard developers themselves!) will offer you a misguided recommendation to start from empty Proguard config + default Android Proguard configuration, and eventually add -keep rules when necessary.

DO NOT DO THAT!!

Those advices come from people, who are either too badass to understand problem of average developer (read: "the Proguard developer himself") or don't have a clue about using Proguard properly. In fact, these kind of misguided practices are the very reason, why many answers to this question warn you against using Proguard: it's default behavior is like suggesting someone to start mountaineering from scaling the Everest.

Default Proguard configuration will obfuscate, shrink and optimize everything—your entire application with all dependencies except some classes you explicitly exclude. You don't want that, unless you have absolute understanding of every library and line of code in your projects: how they work and interact with each other, which techniques they internally use etc.

Instead you want to do the minimal necessary intervention (shrinking the code to reduce dex method count) in the minimal possible range (few hugest libraries) with minimal consequences (only where Proguard is known to work for sure). Here is my Proguard config for such cases:

-dontoptimize
-dontobfuscate

# Prints some helpful hints, always add this option
-verbose

-keepattributes SourceFile,LineNumberTable,Exceptions,InnerClasses,Signature,Deprecated,*Annotation*,EnclosingMethod


# add all known-to-be-safely-shrinkable classes to the beginning of line below
-keep class !com.android.support.**,!com.google.android.**,** { *; }

Add the above rules to your app's proguard-rules.pro, they will shrink only classes, that you explicitly allow to shrink. Append wildcards for other safely shrinkable packages (exactly as above—with ! and .** parts) to beginning of the -keep line.

user1643723
  • 4,109
  • 1
  • 24
  • 48
  • This looks like a good extensive guideline. I will follow it while working with proguard! Thank you very much! – pebbles Jul 20 '15 at 11:26
  • I have a slightly different configuration that I would like to compare with yours. I'll post it as a separate answer as it's really ugly to put code in comments on SO. – Alix Oct 20 '17 at 07:56
  • Completely different really but we have the same goal: Avoid maintenance hell and don't use obfuscation or anything that can make the app brittle... – Alix Oct 20 '17 at 08:34
2

As an alternative to ProGuard you could use the built-in Gradle shrinker by turning off ProGuard but still reference a ProGuard config. This will remove unused code but not obfuscate or do any other "magic". Although recommended only for Debug builds I don't see why you can't use it for Release builds as well if you don't think that you need obfuscation.

The main benefit, compared to ProGuard (in my opinion) is that you avoid tight coupling between a ProGuard configuration and the structure of your codebase and third party dependencies.

build.gradle:

minifyEnabled true
useProguard false
proguardFiles ('proguard-basic.pro', getDefaultProguardFile('proguard-android.txt'))

proguard-basic.pro:

-dontwarn javax.**
-keep class com.mycompany.** { *; }
Alix
  • 2,630
  • 30
  • 72
  • Erm... this configuration does not use Proguard at all. `useProguard false` disables Proguard completely, so value of `proguardFiles` and their corresponding contents make no difference. I don't see, how this configuration make the app less brittle (unless by "making less brittle" you mean not using Proguard to begin with). Also why do you use `-dontwarn javax.**`? This looks really arbitrary. At least make it `-dontwarn javax.annotations.**` or something like that. – user1643723 Oct 20 '17 at 12:12
  • It may look that way but try it. The proguard file is used... By the shrinker. – Alix Oct 20 '17 at 12:22
  • As for the warning of javax: the shrinker complained about them missing. As they are part of the platform on the device I guess that it's okay to suppress it – Alix Oct 20 '17 at 21:48
  • Interesting. I completely missed introduction of the additional shrinker for fast run. It is true, that Proguard is somewhat slow and tends to do unexpected stuff (like removing debug data), so the new shrinker may have some usefulness. I suggest that you clarified, that it is distinct from Proguard (and probably linked to the [specific section](https://developer.android.com/studio/build/shrink-code.html#gradle-shrinker) of the article). – user1643723 Oct 21 '17 at 05:10
0

If you enable minification via ProGuard, it will also minify your dependencies.

Libraries are typically not already obfuscated/minified with ProGuard. Some libraries will not work properly by default if they are obfuscated, so you should check any libraries you use to see if they have any documentation surrounding ProGuard. Butterknife, for example, has a few special ProGuard rules that you need to include to ensure that it continues working properly.

Bryan Herbst
  • 66,602
  • 10
  • 133
  • 120
0

For me you should rather look for multidex, to go beyond 65k limit, not proguard as in longer run the later is not a solution to your problems.

See docs: https://developer.android.com/tools/building/multidex.html

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
  • hm why do u think? i think its more elegant / professional (w.e.) not to have that inflated apk's, isnt it? – pebbles Jul 20 '15 at 01:52
  • 1
    Yes, it's always better to have smaller APKs (and you're probably going to run proguard + minify before going to production anyway) BUT you may still go beyond 65k methods even after minifying. Enabling multidex is the 'right' way to solve the 65k method limit. – ebernie Jul 20 '15 at 03:18
  • So u guys mean enabling multidex is needed anyway to be on the "safe" side? Since it is easy to use, i just implemented it. But i still want to shrink my code ;) – pebbles Jul 20 '15 at 11:34
  • Yes, enabling multidex addresses your problem `directly`. Using proguard **may** (still, temporarily and w/o any warranties) solve the problem but it will be rather **side effect**. But THE right fix is going for `multidex`. – Marcin Orlowski Jul 20 '15 at 12:56
0

If you enable minification in your build.grade file, then yes it will also shrink your dependencies.

Keep in mind that Proguard may introduce unwanted side effects. Not all libraries/dependencies can be shrunk as Proguard also obfuscates the code. (i.e. turns String name into String n) and removes unused code.

Take a look at this Github project: https://github.com/krschultz/android-proguard-snippets

As an alternative, you can look into using MultiDex. You can read about it here: https://developer.android.com/tools/building/multidex.html

michaelcarrano
  • 1,316
  • 2
  • 12
  • 19
0

As per the new Android Studio update in version 3.2 new code shrinker that also obfuscates by adding the line below to your project’s gradle.properties file

Add This line:

android.enableR8 = true
amit pandya
  • 1,384
  • 13
  • 22