Дополнительные типы правил

В R8 можно добавлять правила, влияющие на оптимизацию вашего приложения, помимо правил сохранения. Добавьте эти правила в тот же файл proguard-rules.pro , где вы храните правила сохранения.

Правила делятся на следующие категории:

  • Предположения
    • -assumevalues
    • -assumenosideeffects
  • Другие оптимизации
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Предположения

Эти правила указывают R8, что он может делать определенные предположения о поведении конкретного кода во время выполнения.

-assumevalues

Правило -assumevalues указывает R8, что значение поля или возвращаемое значение метода всегда является определенной константой или находится в пределах заданного диапазона во время выполнения. -assumevalues предназначено для таких вещей, как значения флагов, которые на этапе сборки, как известно, имеют определенные значения во время выполнения.

Стандартный статический анализ R8 может не определять значения членов во время выполнения. С помощью -assumevalues вы указываете R8 предполагать указанное значение или диапазон при оптимизации кода. Это позволяет R8 выполнять агрессивную оптимизацию.

Синтаксис для -assumevalues ​​аналогичен синтаксису для хранения member_specification , но дополнительно включает в себя return clause как показано ниже:

<member_specification> return <value> | <range>

Аргументы <value> и <range> поддерживают следующие значения и типы:

  • Специальные значения: true, false, null, @NonNull
  • Первоначальные значения: int
  • Ссылки на статические поля (включая поля перечислений)

Для определения диапазона используйте формат min..max , включающий все значения. Например, следующий фрагмент кода показывает, что переменная CUSTOM_VAL принимает значения от 26 до 2147483647:

-assumevalues public class com.example.Foo {
    public static int CUSTOM_VAL return 26..2147483647;
}

Это правило можно использовать в следующих ситуациях:

  • Для библиотек : Чтобы гарантировать, что при оптимизации приложений все локальные отладочные механизмы будут удалены из кода общедоступной библиотеки.
  • Для приложений : Чтобы удалить отладочный код из релизного приложения, предпочтительнее использовать варианты сборки и варианты определенных наборов исходных кодов или констант, но если варианты наборов исходных кодов не подходят для вашего случая или если вам нужна более надежная гарантия полного удаления путей выполнения кода, используйте -assumevalues .

В следующем примере показан класс, в котором R8 удаляет инструменты отладки из оптимизированной версии приложения:

package com.example;

public class MyConfig {
    // This field is initialized to false but is overwritten by a resource
    // value or other mechanism in the final build process. R8's static analysis
    // might see the initial 'false' but the runtime value is known to be
    // 'true'.
    public static final boolean IS_OPTIMIZED_VERSION = false;
}

// In another class:
public void initFeatures() {
    if (MyConfig.IS_OPTIMIZED_VERSION) {
        System.out.println("Starting optimized features...");
        android.util.Log.d(TAG, "Starting optimized features...");
        initOptimizedService();
    } else {
        android.util.Log.d(TAG, "Starting debug/logging features...");
        initDebugTools();
    }
}

Следующее правило показывает, как сообщить R8, что переменная IS_OPTIMIZED_VERSION всегда должна быть установлена ​​в true .

-assumevalues class com.example.MyConfig {
    public static final boolean IS_OPTIMIZED_VERSION return true;
}

-assumenosideeffects

Правило -assumenosideeffects указывает R8, что он может предполагать, что указанные члены не имеют побочных эффектов. R8 может полностью удалить вызовы таких методов, которые не возвращают значения или возвращают фиксированное значение.

Синтаксис параметра -assumenosideeffects аналогичен синтаксису для хранения member_specification .

В следующем примере показано, как сообщить R8, что все public static методы с именем log в классе DebugLogger не должны иметь побочных эффектов, что позволяет удалить вызовы этих методов.

-assumenosideeffects class com.example.DebugLogger {
    public static void log(...);
}

Другие оптимизации

Это некоторые более продвинутые оптимизации, которые не включены по умолчанию. При их включении вы разрешаете R8 оптимизировать код в соответствии с инструкциями, в дополнение к оптимизациям по умолчанию.

-convertchecknotnull

Для оптимизации проверок на null можно использовать правило -convertchecknotnull . Это применимо к любому методу, который принимает объект в качестве параметра и генерирует исключение, если объект равен null, аналогично стандартному утверждению Kotlin. Тип исключения и сообщение не обязательно совпадают, но поведение, приводящее к сбою в результате выполнения, одинаково.

Если правило -convertchecknotnull соответствует заданному методу, каждый вызов этого метода заменяется вызовом getClass() для первого аргумента. Вызовы getClass() служат заменой проверки на null и позволяют R8 удалить любые дополнительные аргументы исходной проверки на null, такие как дорогостоящие выделения памяти для строк.

Синтаксис параметра -convertchecknotnull выглядит следующим образом:

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Например, если у вас есть класс Preconditions с методом checkNotNull следующего вида:

class Preconditions {
    fun <T> checkNotNull(value: T?): T {
        if (value == null) {
            throw NullPointerException()
        } else {
            return value
        }
    }
}

Используйте следующее правило:

-convertchecknotnull class com.example.package.Preconditions {
  void checkNotNull(java.lang.Object);
}

Правило преобразует все вызовы checkNotNull() в вызовы getClass по первому аргументу. В этом примере вызов checkNotNull(bar) заменяется вызовом bar.getClass() . Если бы bar был null , bar.getClass() выбросил бы NullPointerException , обеспечивая аналогичный эффект проверки на null, но более эффективно.

-maximumremovedandroidloglevel

Этот тип правил удаляет операторы логирования Android (например, Log.w (...) и Log.isLoggable(...) ) на определенном уровне логирования или ниже.

Синтаксис для maximumremovedandroidloglevel выглядит следующим образом:

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Если вы не укажете необязательный class_specification , R8 применит удаление логов ко всему приложению.

Уровни логарифмирования следующие:

Метка журнала

Уровень лога

ПОДРОБНЫЙ

2

ОТЛАЖИВАТЬ

3

ИНФОРМАЦИЯ

4

ПРЕДУПРЕЖДЕНИЕ

5

ОШИБКА

6

УТВЕРЖДАТЬ

7

Например, если у вас есть следующий код:

class Foo {
  private static final String TAG = "Foo";
  void logSomething() {
    if (Log.isLoggable(TAG, WARNING)) {
      Log.e(TAG, "Won't be logged");
    }
    Log.w(TAG, "Won't be logged");
    Log.e(TAG, "Will be logged");
  }
}

Следуя этому правилу:

# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }

Оптимизированный код выглядит следующим образом:

class Foo {
  private static final String TAG = "Foo";
  void logSomething() {
    Log.e(TAG, "Will be logged");
  }
}

Если для одного и того же метода указано несколько максимальных уровней логирования, R8 использует минимальный уровень. Например, при соблюдении следующих правил:

-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }

Тогда максимальный уровень удаленного логарифма для foo() равен 4.