Dodatkowe typy reguł

R8 umożliwia dodawanie reguł, które wpływają na optymalizację aplikacji, oprócz reguł keep. Dodaj te reguły do tego samego pliku proguard-rules.pro, w którym masz reguły przechowywania.

Reguły dzielą się na te kategorie:

  • Założenia
    • -assumevalues
    • -assumenosideeffects
  • Inne optymalizacje
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Założenia

Te reguły informują R8, że w czasie działania może przyjmować określone założenia dotyczące konkretnego zachowania kodu.

-assumevalues

Reguła -assumevalues informuje R8, że wartość pola lub wartość zwracana metody jest zawsze określoną stałą lub mieści się w zdefiniowanym zakresie w czasie działania programu. -assumevalues jest przeznaczony do takich elementów jak wartości flag, które w momencie kompilacji mają określone wartości w czasie działania.

Standardowa analiza statyczna R8 może nie być w stanie określić wartości w czasie działania elementów. Za pomocą adnotacji -assumevalues informujesz R8, że podczas optymalizacji kodu ma przyjąć określoną wartość lub zakres. Umożliwia to R8 przeprowadzanie agresywnych optymalizacji.

Składnia -assumevalues jest podobna do składni zachowywania member_specification, ale dodatkowo zawiera return clause w ten sposób:

<member_specification> return <value> | <range>

Argumenty <value><range> obsługują te wartości i typy:

  • Wartości specjalne: true, false, null, @NonNull
  • Wartości pierwotne: int
  • Statyczne odwołania do pól (w tym pól wyliczeniowych)

Aby zdefiniować zakres, użyj formatu min..max. Na przykład poniższy fragment kodu pokazuje, że zmienna CUSTOM_VAL przyjmuje wartości od 26 do 2147483647:

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

Możesz użyć tej reguły w tych sytuacjach:

  • W przypadku bibliotek: aby mieć pewność, że podczas optymalizacji aplikacji wszystkie lokalne punkty zaczepienia debugowania zostaną usunięte z kodu biblioteki publicznej.
  • W przypadku aplikacji: aby usunąć z wersji aplikacji kod debugowania. Lepiej jest używać wariantów kompilacji i wariantów określonych zestawów źródeł lub stałych, ale jeśli warianty zestawów źródeł nie działają w Twoim przypadku lub jeśli potrzebujesz większej gwarancji, że ścieżki kodu zostaną całkowicie usunięte, użyj -assumevalues.

Poniższy przykład pokazuje klasę, z której R8 usuwa narzędzia do debugowania w zoptymalizowanej wersji aplikacji:

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();
    }
}

Poniższa reguła pokazuje, jak poinformować R8, że zmienna IS_OPTIMIZED_VERSION powinna zawsze mieć wartość true.

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

-assumenosideeffects

Reguła -assumenosideeffects informuje R8, że może założyć, że określone elementy nie mają efektów ubocznych. R8 może całkowicie usuwać wywołania takich metod, które nie zwracają wartości lub zwracają stałą wartość.

Składnia -assumenosideeffects jest podobna do składni zachowywania member_specification.

Poniższy przykład pokazuje, jak poinformować R8, że wszystkie metody o nazwie log w klasie DebugLogger nie powinny mieć efektów ubocznych, co pozwala usunąć wywołania tych metod.public static

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

Inne optymalizacje

Są to bardziej zaawansowane optymalizacje, które nie są domyślnie włączone. Gdy je włączysz, R8 będzie optymalizować kod zgodnie z instrukcjami, a także przeprowadzać optymalizacje domyślne.

-convertchecknotnull

Możesz użyć reguły -convertchecknotnull, aby zoptymalizować sprawdzanie wartości null. Dotyczy to każdej metody, która przyjmuje parametr obiektu i zgłasza wyjątek, jeśli obiekt ma wartość null, podobnie jak standardowe potwierdzenie w języku Kotlin. Typ i komunikat wyjątku nie muszą być takie same, ale warunkowe działanie powodujące awarię jest takie samo.

Jeśli reguła -convertchecknotnull pasuje do danej metody, każde wywołanie tej metody jest zastępowane wywołaniem getClass() w pierwszym argumencie. Wywołania funkcji getClass() zastępują sprawdzanie wartości null i umożliwiają kompilatorowi R8 usunięcie dodatkowych argumentów pierwotnego sprawdzania wartości null, takich jak kosztowne przydzielanie ciągów znaków.

Składnia elementu -convertchecknotnull jest taka:

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Załóżmy, że masz klasę Preconditions z metodą checkNotNull w tej postaci:

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

Użyj tej reguły:

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

Reguła przekształca wszystkie wywołania funkcji checkNotNull() w wywołania funkcji getClass w pierwszym argumencie. W tym przykładzie wywołanie checkNotNull(bar) jest zastępowane przez bar.getClass(). Jeśli bar ma wartość null, bar.getClass() zgłosi NullPointerException, co da podobny efekt sprawdzania wartości null, ale będzie bardziej wydajne.

-maximumremovedandroidloglevel

Ten typ reguły usuwa instrukcje logowania Androida (np. Log.w(...) i Log.isLoggable(...)) na określonym poziomie logowania lub poniżej niego.

Składnia elementu maximumremovedandroidloglevel jest taka:

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Jeśli nie podasz opcjonalnego parametru class_specification, R8 usunie logi z całej aplikacji.

Poziomy logowania są następujące:

Etykieta dziennika

Poziom logowania

VERBOSE

2

DEBUGUJ

3

INFORMACJE

4

UWAGA

5

BŁĄD

6

ASSERT

7

Jeśli na przykład masz ten kod:

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");
  }
}

Zastosuj tę regułę:

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

Zoptymalizowany kod wygląda tak:

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

Jeśli dla tej samej metody podasz kilka maksymalnych poziomów logowania, R8 użyje poziomu minimalnego. Na przykład przy tych regułach:

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

wtedy maksymalny usunięty poziom logowania dla foo() wynosi 4.