21

Possible Duplicate:
Removing unused strings during ProGuard optimisation

I have an Android application with dozens of logging statements. I'd prefer they wouldn't appear in the release version, so I used Proguard with something like this in the proguard.cfg file:

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

But the problem is that there are a lot of Log.d("something is " + something), and although the Log.d() statement is being removed from the bytecode, the strings are still there.

So, following this answer, I created a simple wrapper class, something along the lines of:

public class MyLogger {
    public static void d(Object... msgs) {
        StringBuilder log = new StringBuilder();
        for(Object msg : msgs) {
            log.append(msg.toString());
        }
        Log.d(TAG, log.toString());
    }
}

Then I edited my proguard.cfg:

-assumenosideeffects class my.package.MyLogger {
    public static *** d(...);
}

But the strings are still found in the generated bytecode!

Other than this, I am using the standard proguard.cfg provided by the Android SDK. Am I doing something wrong?


Edit: after examining the generated bytecode, I saw that the strings were there, but they were not being appended one to another, as I thought. They were being stored in an array. Why? To pass them as variable parameters to my method. It looks like ProGuard doesn't like that, so I modified my logger class like this:

public static void d(Object a) {
    log(a);
}

public static void d(Object a, Object b) {
    log(a, b);
}

(... I had to put like seven d() methods ...)

private static void log(Object... msgs) {
    (same as before)
}

It's ugly, but now the strings are nowhere in the bytecode.

Is this some kind of bug/limitation of ProGuard? Or is it just me not understanding how it works?

Community
  • 1
  • 1
MM.
  • 2,653
  • 4
  • 26
  • 36
  • You might want to avoid proguard hacks and just use a constant: http://stackoverflow.com/questions/4199563/android-util-log-when-publishing-what-can-i-do-not-do If you then use a constant proguard will remove the code lines when it == false. – Blundell Aug 17 '11 at 08:16
  • 2
    It's true that this is a hack, and an ugly one, but I think it's better than putting `if(DEBUG)` everywhere. The resulting code is cleaner and the result is the same in the exported application. Also, for some reason, Eclipse is whining about dead code if the debug constant is false, and I don't like that. Thanks for the idea, though :) – MM. Aug 17 '11 at 14:44
  • Note that there may still be some dead code if you pass basic data types. Proguard does not remove the basic data type to corresponding `Object` conversion. E.g. if you pass an `int`, Proguard will leave in the line `Integer.valueOf(someVar);` – Maik Aug 13 '14 at 03:53

2 Answers2

11

Your solution with different methods for different numbers of arguments is probably the most convenient one. A single method with a variable number of arguments would be nicer, but the current version of ProGuard is not smart enough to remove all the unused code.

The java compiler compiles a variable number of arguments as a single array argument. On the invocation side, this means creating an array of the right size, filling out the elements, and passing it to the method. ProGuard sees that the array is being created and then being used to store elements in it. It doesn't realize (yet) that it then isn't used for actual useful operations, with the method invocation being gone. As a result, the array creation and initialization are preserved.

It's a good practice to check the processed code if you're expecting some particular optimization.

Eric Lafortune
  • 45,150
  • 8
  • 114
  • 106
  • 1
    I've just started using pro guard to strip out logs and am facing the same dead code issue. I'm wondering why is pro guard unable to remove these arrays in subsequent passes. I've seen IDEs pointing out that a local variable is never used. So, it certainly seems possible. – Ravi K Thapliyal Jul 08 '15 at 21:44
5

Proguard doesn't work me, too, and I don't like to create a wrapper around Log, so I've tested the following solutions:

WORKING Solution

final static boolean IsDebugging = false;

and in your code:

if(IsDebugging)
   Log.i(TAG, "Debugging");

consider that you must use final keyword so that IsDebugging become true or false at compile time and log strings doesn't appear in final bytecode.

NOT WORKING (IsDebugMode)

    public static boolean IsDebugMode(Context context) {
    PackageManager pm = context.getPackageManager();
    PackageInfo pi;
    try {
        pi = pm.getPackageInfo(context.getPackageName(),0);
    } catch (NameNotFoundException e) {
        return false;
    }
    return (pi.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

and in my code:

    boolean IsDebugging = Utils.IsDebugMode(this);

    if(IsDebugging)
      Log.i(TAG, "Debugging");

The above solution doesn't print any Log in Logcat but the log string are compiled and exist in final bytecode which is bad.

The other solution was :

NOT WORKING (BuildConfid.DEBUG)

    if(BuildConfig.DEBUG)
      Log.i(TAG, "Debugging");

This is same as IsDebugMode solution. It doesn't print anything but strings are in final bytecode. Bad again!

Mohsen Afshin
  • 13,273
  • 10
  • 65
  • 90