その他のルールの種類

R8 では、keep ルールとは別に、アプリの最適化に影響するルールを追加できます。これらのルールは、保持ルールを管理するのと同じ 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();
    }
}

次のルールは、変数 IS_OPTIMIZED_VERSION が常に true に設定されることを R8 に伝える方法を示しています。

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

-assumenosideeffects

-assumenosideeffects ルールは、指定されたメンバーに副作用がないと想定できることを R8 に伝えます。R8 は、戻り値がないメソッドや固定値を返すメソッドの呼び出しを完全に削除できます。

-assumenosideeffects の構文は、member_specification を保持する構文に似ています。

次のサンプルは、DebugLogger クラス内の log という名前のすべての public static メソッドに副作用がないことを R8 に伝える方法を示しています。これにより、これらのメソッドの呼び出しを削除できます。

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

その他の最適化

これらは、デフォルトでは有効になっていない高度な最適化です。これらを有効にすると、R8 はデフォルトの最適化に加えて、指示されたとおりにコードを最適化できます。

-convertchecknotnull

-convertchecknotnull ルールを使用して、null チェックを最適化できます。これは、オブジェクト パラメータを受け取り、オブジェクトが null の場合に標準の Kotlin アサーションと同様に例外をスローするメソッドに適用されます。例外の型とメッセージは必ずしも同じではありませんが、条件付きのクラッシュ動作は同じです。

-convertchecknotnull ルールが特定のメソッドと一致する場合、そのメソッドの各呼び出しは、最初の引数に対する getClass() の呼び出しに置き換えられます。getClass() の呼び出しは null チェックの代わりとなり、R8 は元の null チェックの余分な引数(高コストの文字列割り当てなど)を削除できます。

-convertchecknotnull の構文は次のとおりです。

-convertchecknotnull <class_specification> {
   <member_specification>;
}

たとえば、次のようにメソッド checkNotNull を持つクラス Preconditions があるとします。

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() に置き換えられます。barnull の場合、bar.getClass()NullPointerException をスローし、同様の null チェック効果をより効率的に実現します。

-maximumremovedandroidloglevel

このルールタイプは、特定のログレベル以下の Android ロギング ステートメント(Log.w(...) や Log.isLoggable(...) など)を削除します。

maximumremovedandroidloglevel の構文は次のとおりです。

-maximumremovedandroidloglevel <log_level> [<class_specification>]

オプションの class_specification を指定しない場合、R8 はアプリ全体にログ削除を適用します。

ログレベルは次のとおりです。

ログラベル

ログレベル

VERBOSE

2

DEBUG

3

INFO

4

警告

5

ERROR

6

ASSERT

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 になります。