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> i <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.