Удаление неиспользуемых строк во время оптимизации 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>