Удаление неиспользуемых строк во время оптимизации ProGuard

Я включаю эту конфигурацию ProGuard, чтобы отключить отладочные сообщения журнала при выпуске приложения для Android:

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

Это работает как ожидалось — Из журналов ProGuard и выхода журнала Android вы можете видеть, что такие вызовы, как Log.d("This is a debug statement");, удаляются.

Однако, если я декомпилирую приложение на этом этапе, я все еще вижу все литералы String, которые были использованы — т.е. This is a debug statement в этом примере.

Есть ли способ удалить каждый String, который больше не нужен для байт-кода?

Ответ 1

ProGuard может удалить простые константные аргументы (строки, целые числа и т.д.). Поэтому в этом случае код и строковая константа должны полностью исчезнуть:

Log.d("This is a debug statement");

Однако, возможно, вы заметили проблему с некоторым кодом, подобным этому:

Log.d("The answer is "+answer);

После компиляции это фактически соответствует:

Log.d(new StringBuilder().append("The answer is ").append(answer).toString());

ProGuard версии 4.6 может упростить это примерно так:

new StringBuilder().append("The answer is ").append(answer).toString();

Итак, ведение журнала исчезло, но шаг оптимизации по-прежнему оставляет некоторый пух. На удивление сложно упростить это без каких-либо более глубоких знаний о классе StringBuilder. Что касается ProGuard, он может сказать:

new DatabaseBuilder().setup("MyDatabase").initialize(table).close();

Для человека код StringBuilder, очевидно, может быть удален, но код DatabaseBuilder, вероятно, не может. ProGuard требует анализа escape-кода и нескольких других методов, которые еще не включены в эту версию.

Что касается решения: вы можете создать дополнительные методы отладки, которые принимают простые аргументы, и пусть ProGuard удалит их:

MyLog.d("The answer is ", answer);

В качестве альтернативы вы можете попробовать префикс каждого оператора отладки с условием, что ProGuard может позже оценить как false. Этот параметр может быть немного более запутанным, для чего требуется дополнительный параметр -assumenosideeffects для метода инициализации флага отладки.

Ответ 2

вот как мы это делаем - с помощью ant task

<target name="base.removelogs">
    <replaceregexp byline="true">
        <regexp pattern="Log.d\s*\(\s*\)\s*;"/>
        <substitution expression="{};"/>
        <fileset dir="src/"><include name="**/*.java"/></fileset>
    </replaceregexp>
</target>

Ответ 3

Поскольку мне не хватает комментариев, чтобы прокомментировать ответ задачи ant напрямую, здесь некоторые исправления для него, поскольку это оказывается очень полезным в сочетании с CI-сервером, таким как Jenkins, который может выполнить его для сборки релиза

<target name="removelogs">
    <replaceregexp byline="true">
        <regexp pattern="\s*Log\.d\s*\(.*\)\s*;"/>
        <substitution expression="{};"/>
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>

"." после того, как журнал должен быть экранирован, а "." внутри скобок задает любой оператор регистрации, а не просто пробелы, как "\ s *".

Поскольку у меня нет большого опыта работы с RegEx, я надеюсь, что это поможет некоторым людям в той же ситуации получить эту задачу ant, работающую (например, на Jenkins).

Ответ 4

Если вы хотите поддерживать многострочные вызовы журналов, вы можете использовать это регулярное выражение:

(android\.util\.)*Log\[email protected]([ewidv]|wtf)\s*\([\S\s]*?\)\s*;

Вы должны использовать это в рамках задачи ant replaceregexp, например:

<replaceregexp>
    <regexp pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)"/>
    <substitution expression="if(false){\1}"/>
    <fileset dir="src/">
        <include name="**/*.java"/>
    </fileset>
</replaceregexp>

Примечание. Это окружает вызовы журнала с помощью if(false){ и }, поэтому исходный вызов сохраняется, как для справки, так и для сохранения номеров строк при проверке промежуточных файлов сборки, позволяя компилятору java блокировать вызовы во время компиляции.

Если вы предпочитаете полностью удалять логические вызовы, вы можете сделать это следующим образом:

<replaceregexp>
    <regexp pattern="(android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;"/>
    <substitution expression=""/>
    <fileset dir="src/">
        <include name="**/*.java"/>
    </fileset>
</replaceregexp>

Вы также можете применить регулярное выражение как фильтр в задаче <copy>, например:

<copy ...>
    <fileset ... />
    <filterchain>
        <tokenfilter if:true="${strip.log.calls}">
            <stringtokenizer delims=";" includeDelims="true"/>
            <replaceregex pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)" replace="if(false){\1}"/>
        </tokenfilter>
    </filterchain>
    <!-- other-filters-etc -->
</copy>