Zusätzliche Regeltypen

Mit R8 können Sie zusätzlich zu den Keep-Regeln auch andere Regeln hinzufügen, die sich auf die Optimierung Ihrer App auswirken. Fügen Sie diese Regeln in derselben proguard-rules.pro-Datei hinzu, in der Sie Ihre Aufbewahrungsregeln verwalten.

Die Regeln lassen sich in folgende Kategorien einteilen:

  • Annahmen
    • -assumevalues
    • -assumenosideeffects
  • Weitere Optimierungen
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Annahmen

Diese Regeln weisen R8 an, dass es bestimmte Annahmen über das Verhalten von bestimmtem Code zur Laufzeit treffen kann.

-assumevalues

Die -assumevalues-Regel weist R8 an, dass der Wert eines Felds oder der Rückgabewert einer Methode zur Laufzeit immer eine bestimmte Konstante ist oder in einem definierten Bereich liegt. -assumevalues ist für Dinge wie Flag-Werte vorgesehen, von denen zur Build-Zeit bekannt ist, dass sie zur Laufzeit bestimmte Werte haben.

Die standardmäßige statische Analyse von R8 kann die Laufzeitwerte von Mitgliedern möglicherweise nicht ermitteln. Mit -assumevalues weisen Sie R8 an, beim Optimieren des Codes den angegebenen Wert oder Bereich anzunehmen. So kann R8 aggressive Optimierungen vornehmen.

Die Syntax für -assumevalues ähnelt der Syntax für das Beibehalten eines member_specification, enthält aber zusätzlich ein return clause:

<member_specification> return <value> | <range>

Die Argumente <value> und <range> unterstützen die folgenden Werte und Typen:

  • Spezielle Werte: true, false, null, @NonNull
  • Primitive Werte: int
  • Statische Feldverweise (einschließlich Enum-Felder)

Verwenden Sie das inklusive Format min..max, um einen Bereich zu definieren. Im folgenden Snippet wird beispielsweise gezeigt, dass die Variable CUSTOM_VAL Werte zwischen 26 und 2147483647 akzeptiert:

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

Sie können diese Regel in den folgenden Situationen verwenden:

  • Für Bibliotheken: Damit beim Optimieren von Apps alle lokalen Debugging-Hooks aus dem öffentlichen Bibliothekscode entfernt werden.
  • Für Apps: Um beispielsweise Debug-Code aus einer Release-App zu entfernen. Es ist vorzuziehen, Build-Varianten und Varianten bestimmter Quellsets oder Konstanten zu verwenden. Wenn Variant-Quellsets jedoch nicht für Ihren Fall geeignet sind oder Sie eine stärkere Garantie benötigen, dass die Codepfade vollständig entfernt werden, verwenden Sie -assumevalues.

Im folgenden Beispiel wird eine Klasse gezeigt, in der R8 Debugging-Tools aus der optimierten Version einer App entfernt:

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

Die folgende Regel zeigt, wie Sie R8 mitteilen, dass die Variable IS_OPTIMIZED_VERSION immer auf true gesetzt sein muss.

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

-assumenosideeffects

Die -assumenosideeffects-Regel weist R8 an, dass davon ausgegangen werden kann, dass die angegebenen Elemente keine Nebenwirkungen haben. R8 kann Aufrufe solcher Methoden, die keine Rückgabewerte haben oder einen festen Wert zurückgeben, vollständig entfernen.

Die Syntax für -assumenosideeffects ähnelt der Syntax für das Beibehalten von member_specification.

Das folgende Beispiel zeigt, wie Sie R8 mitteilen, dass alle public static-Methoden mit dem Namen log in der Klasse DebugLogger keine Nebeneffekte haben sollen. Dadurch können Aufrufe dieser Methoden entfernt werden.

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

Weitere Optimierungen

Dies sind einige erweiterte Optimierungen, die nicht standardmäßig aktiviert sind. Wenn Sie sie aktivieren, kann R8 Code wie angegeben optimieren, zusätzlich zu den Standardoptimierungen.

-convertchecknotnull

Mit der -convertchecknotnull-Regel können Sie Nullprüfungen optimieren. Dies gilt für jede Methode, die einen Objektparameter verwendet und eine Ausnahme auslöst, wenn das Objekt null ist, ähnlich wie bei einer Standard-Kotlin-Assertion. Der Ausnahmetyp und die Meldung sind nicht unbedingt identisch, aber das bedingte Absturzverhalten ist es.

Wenn eine -convertchecknotnull-Regel mit einer bestimmten Methode übereinstimmt, wird jeder Aufruf dieser Methode durch einen Aufruf von getClass() für das erste Argument ersetzt. Die Aufrufe von getClass() dienen als Ersatz für die Nullprüfung und ermöglichen es R8, alle zusätzlichen Argumente der ursprünglichen Nullprüfung zu entfernen, z. B. teure Stringzuweisungen.

Die Syntax für -convertchecknotnull lautet so:

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Angenommen, Sie haben die Klasse Preconditions mit der Methode checkNotNull wie folgt:

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

Verwenden Sie die folgende Regel:

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

Die Regel wandelt alle Aufrufe von checkNotNull() in Aufrufe von getClass für das erste Argument um. In diesem Beispiel wird ein Aufruf von checkNotNull(bar) durch bar.getClass() ersetzt. Wenn bar gleich null wäre, würde bar.getClass() eine NullPointerException auslösen. Das Ergebnis wäre ähnlich wie bei der Nullprüfung, aber effizienter.

-maximumremovedandroidloglevel

Bei diesem Regeltyp werden Android-Logging-Anweisungen (wie Log.w(...) und Log.isLoggable(...)) auf oder unter einem bestimmten Log-Level entfernt.

Die Syntax für maximumremovedandroidloglevel lautet so:

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Wenn Sie class_specification nicht angeben, wendet R8 das Entfernen von Protokollen auf die gesamte App an.

Folgende Logebenen sind verfügbar:

Label für Log

Protokollebene

VERBOSE

2

FEHLER BEHEBEN

3

INFO

4

WARNUNG

5

FEHLER

6

ASSERT

7

Angenommen, Sie haben den folgenden Code:

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

Mit der folgenden Regel:

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

Der optimierte Code sieht so aus:

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

Wenn Sie mehrere maximale Logebenen für dieselbe Methode angeben, verwendet R8 die niedrigste Ebene. Beispiel:

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

ist die maximale entfernte Protokollebene für foo() 4.