I'm developing a big application how has many libraries, I reached the 65k methods limit and I want to clean some libraries to shrink the methods size. Now I'm using proguard, but this take 5 minutes to compile! so, I need a method to clean before proguard (like exclude with build.gradle or something like that)
I'm trying to use something like this :
compile ('com.google.android.gms:play-services:4.3.+'){
exclude group: 'com.google.android.gms.drive'
exclude group: 'com.google.android.gms.games'
}
but it doesn't works , any idea?
If you disable ProGuard's optimization step (-dontoptimize) and obfuscation step (-dontobfuscate), ProGuard may be fast enough for debug builds. The shrinking step can go a long way in reducing the number of methods.
Could you be running into GRADLE-2964 which occurs because Gradle is not using Zip64? This is fixed, according to jira, so maybe it works with a snapshot of Gradle.
Example using snapshot of Gradle 1.12:
gradle-wrapper.properties:
distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.12-20140327133732+0000-bin.zip
Then in whatever task that is zipping (extends from ZipTask) slap on this:
{
....
zip64 = true
....
}
Related
As the title says, is it safe to exclude libraries assets from an Android Gradle APK build ?
One of the Google's libraries is importing a font (~7mb).
Could this file be excluded safely to reduce APK size?
My current method to exclude files is to use an ignoreAssetsPattern statement:
android {
...
buildTypes {
...
release {
...
aaptOptions {
ignoreAssetsPattern '!NotoColorEmojiCompat.ttf:'
}
}
}
}
is it safe to exclude libraries assets from an Android Gradle APK build ?
Generally speaking, no. The library is expecting the asset to be there.
If you feel that you have sufficient test coverage, and so you feel comfortable that you are not using the feature of this library that requires this asset, you're welcome to try excluding it.
IMHO, a better solution is to figure out what library this is coming from (my guess: Jetpack's Emoji library) and remove your code that depends upon it, so you can then remove the whole library from the project.
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'
}
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.
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.** { *; }
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.
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
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
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
I'm currently discovering Android Studio and Gradle and migrating all the build chain of my project from bash scripts to Gradle configurations. It's probably going to be awesome in the end, but meanwhile I'm struggling.
What I want to do now is quite simple. I have a standard rule to generate Javadoc from my source (I took this snippet from http://snowdream.github.io/blog/android/2013/11/01/how-to-generate-javadocs-with-android-gradle-plugin/):
android.libraryVariants.all { variant ->
task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
source = variant.javaCompile.source
def androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath = files(variant.javaCompile.classpath.files, androidJar)
options {
links "http://docs.oracle.com/javase/7/docs/api/"
linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
}
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
}
But my project also contains AIDL files and I don't want these aidl files (nor the .java files generated from them) to be included to the Javadoc.
I tried the rule:
exclude "**/$buildDir/**"
... and I tried a thousand others, but none works and my interfaces and Stub are processed into HTML files.
I beg for your help! Thanks a lot.
After hours of research about how Gradle and Groovy work, the best way I found is:
exclude {
it.file.path.contains('aidl')
}
However, I'm still unsatisfied. I do feel something less brutal could be done using variant.aidlCompile.sourceOutputDir that points to the Java files generated by AIDL. But I could not compare the iterated elements in the Closure to this File, or FileTree, or whatever shape I give it...
EDIT:
After further research, another method is to hide the interfaces defined in AIDL using a custom Doclet that supports #hide tag (like Doclava). I preferred this method because it doesn't cause errors during Javadoc generation.
I exported my project from Eclipse and imported to Android Studio using the instructions in this link: http://developer.android.com/sdk/installing/migrate.html
When I build, I have an error:
Duplicate files copied in APK META-INF/DEPENDENCIES
After searching, I found a solution: add
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
}
into build.gradle. And it works!
But I don't understand why I had this error and why I've had to apply that fix. Can anybody explain?
While Scott Barta's answer is correct, is lacks a simple and common solution: just add
android {
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
}
to your build.gradle to ignore those duplicates.
In Android Gradle builds, you're not permitted to include the same file with the same path more than once in the output. In your build, there were two META-INF/DEPENDENCIES files coming from different places. Since you don't need this file at all in your application, the simplest thing to do is to tell the build system to ignore it altogether, which is what this exclude directive does.
There's also a pickFirst directive to tell the build system to keep one of the copies; there's a tiny amount of detail on that in Android Gradle plugin 0.7.0: "duplicate files during packaging of APK".
Android builds in Gradle are rather strict about duplicate files, which can make life difficult. There's a similar problem if you include the same Java class more than once, where you get the "Multiple dex files define" error (see Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat) for a typical example).
Other build systems are more lenient. It's typical in Java that if you include the same class more than once in a classpath, for example, the first copy it sees is the one that's used; duplicates after that are ignored. This is in most cases easier to deal with, but it has a couple problems. The biggest one is that there can be subtle errors if multiple different versions of a file creep into the build without you knowing -- it can be difficult to figure out what's going on. When you do figure it out, you can usually solve it by juggling the order in which things are included to make sure the one you want makes it to the final output, but in very complex builds, this can be difficult to achieve, and it can happen that doing seemingly unrelated things like including new libraries in your project can upset the ordering and lead to a lot of woe.
For that reason, Gradle has the philosophy of not relying on ordering of things to determine "winners" in the game of resolving duplicates, and it forces the developer to make all dependencies explicit. Android's implementation of its build system on top of Gradle follows that philosophy.
The simplest solution is to add
packagingOptions {
pickFirst 'META-INF/*'
}
to your build.gradle in android section
The easiest way I've found to resolve this problem is to use a wildcard, so you don't find yourself having to manually declare each file in conflict.
packagingOptions {
pickFirst '**'
}
In case that anyone having these problem while uploading new .apk to Google Play Store, after updatng Android Studio ;
click V1 Jar Signature not Full Apk Signature while Generating new Apk with old Keystore
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.google.android.gms:play-services-ads:10.2.1'
implementation 'com.android.support:support-annotations:25.0.1'
testImplementation 'junit:junit:4.12'
**// select only one in two line below** implementation ‘package’ //implementation project(‘:package’)
}
// good luck
I am getting the following error when I compile my app:
[2014-05-07 21:48:42 - Dex Loader] Unable to execute dex: Cannot merge new index 65536 into a non-jumbo instruction!
I am at the point that if I declare a new method anywhere in my package, I get this error. If I don't, the app compiles.
I would like to know what exactly (and accurately) does this error mean. My app is big, but I don't think its that big! So:
Does the error mean I have too many methods? public? static? package? members?
Is it related to the methods/members of my root package, or also to the included JAR libraries?
Is there a way to get more debug information about this?
I already know about that "jumbo" enabling flag addressed in the similar questions here in SO, however, I think jumbo mode is not available on the API level I'm targeting (ICS).
Your error is for the amount of strings (methods, members, etc) in a single dex file.
You need to compile you app using jumbo in dex with:
dex.force.jumbo=true
in project.properties
This increment the limit for strings in a dex files. And your project will probably compile.
Also with jumbo set, the is another limit of 64K only for methods in an single dex. If you get this limit in the future , you will need to remove some dependencies.
UPDATE: for build with Gradle:
In Gradle you can enable jumboMode also in the build.gradle file with:
dexOptions {
jumboMode = true
}
Check:
Android Build: Dex Jumbo Mode in Gradle
Also with Gradle you can avoid the 64K limit for methods using multidex build, tutorial here:
https://developer.android.com/tools/building/multidex.html
For gradle build, just add the dexOptions into build.gradle to enable jumbo mode:
android {
dexOptions {
jumboMode = true
}
}
Remember to run "gradle clean" before your new building.
It's related to the number of methods of libraries included in the project. For example if you have tracking in your app, just Google Analytics is ~7000 methods.
In one of my projects using Lombok (2MB of JAR) gave me these problem. Solved getting rid of this library.
It looks like the problem occurs because all the class files from your project and JAR files are packed together before DEXing. This may not be completely true but any way of controlling this in our project has proven to be quite difficult. Even removing stuff that initially caused this problem, cleaning and rebuilding didn't fix the issue for us in a consistent way.
So we took this opportunity to switch our project to Android Studio and managed to solve the problem by turning on ProGuard for debug builds as well. More precisely we only use the shrink phase of the ProGuard's processing chain.
Gradle makes it very easy to turn on ProGuard for debug builds:
buildTypes {
debug {
runProguard true
proguardFile 'proguard-project-debug.txt'
}
}
And here is the debug ProGuard config we use:
-keep class com.your.code.**
# Use -keep to explicitly keep any other classes shrinking would remove
-dontoptimize
-dontobfuscate
-ignorewarnings
This does increase the build time of the project but the good side is that the debugger still works.
The only faster alternative I can think of is that any JAR files are manually stripped of the unused class files. But this is not only difficult to do it is also inconvenient when you want to use a slightly larger part of a library at a later time.
I hope this helps other developers struggling with this issue. And perhaps in the future Google can improve the compiler that does this pruning by default. Our APK DEX file went from 8MB to 2.9MB.
Newer gradle (1.0.0+) versions
In newer Versions of Android studio (1.0+) the bundled Gradle got updated. There were some changes on how the build mechanism works so your project Gradle file can now take advantage of the minifyEnabled and shrinkResources parameters. Current version is 1.1.0.
Keeping up with changes on a fast moving platform like Android takes effort but it is often rewarded with new features, tools and faster build times. So updating Android Studio and (carefully) updating your projects is worth the time you invest.
buildTypes {
debug {
proguardFile 'proguard-project-debug.txt'
minifyEnabled true
shrinkResources true
}
}
Some interesting observations. Same error may appear if you have multi-flavor project. It's confusing. Turned out that I attempted run app with generic command: gradlew installDebug. When I've changed command line to look like this problem is gone. Don't forget to replace Flavor part with your actual one.
gradlew installFlavorDebug