Showing posts with label Proguard. Show all posts
Showing posts with label Proguard. Show all posts

Tuesday, August 20, 2013

Proguard gotcha

A while ago I wrote about removing the logs from release builds using Proguard. As usual, I've found a gotcha that might cost you a couple hours of head scratching.

Let's say that we have a code like this somewhere:
package com.porcupineprogrammer.proguardgotcha;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {
  static final String TAG = "ProguardGotcha";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Log.d(TAG, doNotRunOnProduction());
  }

  private String doNotRunOnProduction() {
    Log.e(TAG, "FIRE ZE MISSILES!");

    return "Harmless log message";
  }
}
The doNotRunOnProduction() method might perform some expensive database query, send some data over the network or launch intercontinental missiles - anyways do something that you don't want to happen in production app. If you run the code on the debug build you'll of course get the following logs.
08-20 19:31:34.183    1819-1819/com.porcupineprogrammer.proguardgotcha E/ProguardGotcha: FIRE ZE MISSILES!
08-20 19:31:34.183    1819-1819/com.porcupineprogrammer.proguardgotcha D/ProguardGotcha: Harmless log message
Now, let's add Proguard config that removes all the Log.d() calls:
-assumenosideeffects class android.util.Log {
  public static *** d(...);
}
We might expect the Log.e() call to be gone as well, but alas, here is what we get:
08-20 19:34:45.733    2078-2078/com.porcupineprogrammer.proguardgotcha E/ProguardGotcha: FIRE ZE MISSILES!
The key point to understanding what is happening here is the fact that the Proguard does not operate on the source code, but on the compiled bytecode. In this case, what the Proguard processes is more like this code:
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  String tmp = doNotRunOnProduction();
  Log.d(TAG, tmp);
}
One might argue that the temporary variable is not used and Proguard should remove it as well. Actually that might happen if you add some other Proguard configuration settings, but the point of this blog post is that when you specify that you want to remove calls to Log.d(), you shouldn't expect that any other calls will be affected. They might, but if your code really launches the missiles (or does something with similar effect for your users), you don't want to bet on this.

Wednesday, February 20, 2013

Guava on Android

In November 2012, the revision 21 of Android SDK Tools was released and one of the items in the release notes made me a very happy panda:
Improved the build time by pre-dexing libraries (both JAR files and library projects).
This change solved the most problematic issue with Guava and other large libraries - build time. Before this change Android tools executed dex for your code and every referenced library every time you wanted to launch the application, which in case of Guava took ages and required increasing heap space for Java VM, because the Eclipse closed with "Unable to execute dex: Java heap space" error.

IntelliJ users could work around this issue by enabling Proguard for debug builds, which could reduce the size of dex input by removing unused code. Eclipse users might try generalizing the Treeshaker plugin, which does pretty much the same inside a custom compilation step added before dexing. But there was no straight way to use Guava and keep the build times on the sane level.

Now the first build still takes ages, and the Eclipse still crashes if you don't bump its heap space, but for all consecutive builds everything works blazing fast. Goodbye hand rolled stuff, welcome immutable collections, fluent comparators, hashCode helper and tons of other goodness. I keep finding in our code base whole chunks of code which can be replaced with one or two lines utilizing Guava features. I plan to post a summary of those changes.

Final note: if you are using Proguard, remember to add Guava specific entries mentioned in the documentation.

Wednesday, October 17, 2012

Android protip: remove debug logs from release builds with Proguard

I use the android.util.Log extensively - often it's faster than starting the debugger and (unlike debugging) it's always on, which is invaluable when you're trying to track the root cause of some hard to reproduce bugs. Logging is also nice for release candidate builds you give to the QA team - if they find some bugs you'll have more information than just a stack trace.

On the other hand you don't want to keep all those logs for release builds, mostly for performance and privacy reasons. If you google around  for a solution to this issue you'll probably find a dozen ideas like using a wrapper for logging class, using a dedicated logging framework or even some sed/awk scripts to ant build process. Among those ideas is the one I'd like to recommend: customizing Proguard configuration.

Proguard is automatically run in release builds by ant and Export wizards from Eclipse and there is an optimization option, which can be used to disable logs:

-assumenosideeffects
Specifies methods that don't have any side effects (other than maybe returning a value). In the optimization step, ProGuard will then remove calls to such methods, if it can determine that the return values aren't used.
I wouldn't use this tool for the purpose stated in the manual, because even if my code doesn't have any side effects, the methods called from it might have ones; I'd rather use some static code analysis tool to find the unnecessary calls and manually remove them. It looks perfect for suppressing the logs though and it's very simple to set up - just add the following lines to the proguard-project.txt file generated by android command line tool:

-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

You can of course keep some of the priority levels by removing lines from this config.

The nice thing about this solution is that you have to set it only once and it will just work for all further release builds.

UPDATE (August 2013): do not assume that Proguard will remove any code other than the methods listed in assumenosideeffects setting. See "Proguard gotcha" post for more details.