保持ルールを追加する

大まかに言うと、保持ルールは、クラス(またはサブクラスや実装)を指定し、そのクラス内で保持するメンバー(メソッド、コンストラクタ、フィールド)を指定します。

keep ルールの一般的な構文は次のとおりです。


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

次の例は、保持オプションとして keepclassmembers、修飾子として allowoptimization を使用し、com.example.MyClass から someSpecificMethod() を保持する保持ルールの例です。

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

Keep オプション

保持オプションは、保持ルールの最初の部分です。保持するクラスの要素を指定します。keep、keepclassmembers、keepclasseswithmembers、keepnames、keepclassmembernames、keepclasseswithmembernames の 6 つの保持オプションがあります。

次の表に、これらの保持オプションを示します。

Keep オプション 説明
keepclassmembers R8 が指定されたメンバーを含むクラスを削除しない場合にのみ、指定されたメンバーを保持します。
keep 指定されたクラスと指定されたメンバー(フィールドとメソッド)を保持し、最適化されないようにします。

注: keep は、一致するクラスで最適化が行われないようにするため、通常は keep オプション修飾子でのみ使用する必要があります。keep
keepclasseswithmembers クラスにクラス仕様のすべてのメンバーが含まれている場合にのみ、クラスとその指定されたメンバーを保持します。
keepclassmembernames 指定されたクラスメンバーの名前変更を防止しますが、クラスまたはそのメンバーの削除は防止しません。

注: このオプションの意味は誤解されることが多いため、代わりに同等の -keepclassmembers,allowshrinking の使用を検討してください。
keepnames クラスとそのメンバーの名前変更を防ぎますが、未使用と見なされた場合に完全に削除されるのを防ぐことはできません。

注: このオプションの意味は誤解されることが多いため、代わりに同等の -keep,allowshrinking を使用することを検討してください。
keepclasseswithmembernames クラスとその指定されたメンバーの名前が変更されないようにします。ただし、メンバーが最終コードに存在する場合に限ります。コードの削除は防止されません。

注: このオプションの意味は誤解されることが多いため、代わりに同等の -keepclasseswithmembers,allowshrinking を使用することを検討してください。

適切な保存オプションを選択する

適切な keep オプションを選択することは、アプリの適切な最適化を決定するうえで重要です。特定の keep オプションはコードを圧縮します。これは、参照されていないコードを削除するプロセスです。一方、他のオプションはコードを難読化(名前変更)します。次の表に、さまざまな keep オプションのアクションを示します。

Keep オプション クラスを圧縮する クラスを難読化する メンバーを縮小する メンバーを難読化する
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

オプション修飾子を保持する

keep オプション修飾子は、keep ルールのスコープと動作を制御するために使用されます。keep ルールには、0 個以上の keep オプション修飾子を追加できます。

keep オプション修飾子の可能な値は、次の表のとおりです。

値 説明
allowoptimization 指定した要素を最適化できます。ただし、指定された要素の名前変更や削除は行われません。
allowobfuscation 指定された要素の名前変更を許可します。ただし、要素は削除されず、最適化もされません。
allowshrinking R8 が指定された要素への参照を検出しない場合、その要素を削除できるようにします。ただし、要素の名前が変更されたり、最適化されたりすることはありません。
includedescriptorclasses 保持されているメソッド(パラメータの型と戻り値の型)とフィールド(フィールドの型)の記述子に現れるすべてのクラスを保持するよう R8 に指示します。
allowaccessmodification 最適化プロセス中に、R8 がクラス、メソッド、フィールドのアクセス修飾子(public、private、protected)を変更(通常は拡張)できるようにします。
allowrepackage R8 がクラスを別のパッケージ(デフォルト(ルート)パッケージを含む)に移動できるようにします。

クラスの仕様

すべての keep ルールでクラス(インターフェース、列挙型、アノテーション クラスを含む)を指定する必要があります。必要に応じて、アノテーションに基づいてルールを制限できます。これには、スーパークラスまたは実装されたインターフェースを指定するか、クラスのアクセス修飾子を指定します。java.lang.String などの java.lang 名前空間のクラスを含むすべてのクラスは、完全修飾 Java 名を使用して指定する必要があります。使用する名前を把握するには、生成された Java 名を検査するで説明されているツールを使用してバイトコードを検査します。

次の例は、MaterialButton クラスを指定する方法を示しています。

  • 適切: com.google.android.material.button.MaterialButton
  • 不適切: MaterialButton

クラス仕様では、保持するクラス内のメンバーも指定します。たとえば、次のルールは MyClass クラスと someSpecificMethod() メソッドを保持します。

-keep class com.example.MyClass {
  void someSpecificMethod();
}

アノテーションに基づいてクラスを指定する

アノテーションに基づいてクラスを指定するには、アノテーションの完全修飾 Java 名の前に @ 記号を付けます。次に例を示します。

-keep class @com.example.MyAnnotation com.example.MyClass

keep ルールに複数のアノテーションがある場合、リストされているすべてのアノテーションを持つクラスが保持されます。複数のアノテーションをリストできますが、ルールは、クラスにリストされたすべてのアノテーションがある場合にのみ適用されます。たとえば、次のルールでは、Annotation1 と Annotation2 の両方でアノテーションが付けられたすべてのクラスが保持されます。

-keep class @com.example.Annotation1 @com.example.Annotation2 *

サブクラスと実装を指定する

サブクラスまたはインターフェースを実装するクラスをターゲットにするには、それぞれ extend と implements を使用します。

たとえば、次のようにサブクラス Foo を持つクラス Bar があるとします。

class Foo : Bar()

次の保持ルールは、Bar のすべてのサブクラスを保持します。keep ルールには、スーパークラス Bar 自体は含まれません。

-keep class * extends Bar

インターフェース Bar を実装するクラス Foo がある場合:

class Foo : Bar

次の keep ルールは、Bar を実装するすべてのクラスを保持します。keep ルールには、インターフェース Bar 自体は含まれません。

-keep class * implements Bar

アクセス修飾子に基づいてクラスを指定する

public、private、static、final などのアクセス修飾子を指定して、保持ルールをより正確にすることができます。

たとえば、次のルールは、api パッケージとそのサブパッケージ内のすべての public クラスと、これらのクラス内のすべての public メンバーと protected メンバーを保持します。

-keep public class com.example.api.** { public protected *; }

クラス内のメンバーに修飾子を使用することもできます。たとえば、次のルールでは、Utils クラスの public static メソッドのみが保持されます。

-keep class com.example.Utils {
  public static void *(...);
}

Kotlin 固有の修飾子

R8 は、internal や suspend などの Kotlin 固有の修飾子をサポートしていません。このようなフィールドを保持するには、次のガイドラインを使用します。

  • internal クラス、メソッド、フィールドを保持するには、それらを public として扱います。たとえば、次の Kotlin ソースについて考えてみましょう。

    package com.example
    internal class ImportantInternalClass {
      internal val f: Int
      internal fun m() {}
    }
    

    internal クラス、メソッド、フィールドは、Kotlin コンパイラによって生成された .class ファイルでは public であるため、次の例に示すように public キーワードを使用する必要があります。

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • suspend メンバーがコンパイルされるときに、コンパイルされたバイトコードのシグネチャを keep ルールで照合します。

    たとえば、Kotlin で次のように定義された、具体的な実世界のリポジトリ クラスについて考えてみましょう。

    package com.example.repository
    
    import com.example.model.User
    
    class UserRepository {
        suspend fun fetchUser(id: String): User {
            // Implementation details...
        }
    }
    

    Kotlin コンパイラがこのクラスをバイトコードにコンパイルすると、suspend 関数は継続渡しスタイル(CPS)変換を受けます。suspend 修飾子が削除され、戻り値の型が java.lang.Object に変更され、非同期ステート マシンを管理するために kotlin.coroutines.Continuation パラメータがメソッド シグネチャに追加されます。

    バイトコードのコンパイル済みメソッド シグネチャは次のようになります。

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    この関数の keep ルールを記述するには、Kotlin の suspend 構文を使用できません。代わりに、コンパイルされたバイトコード署名(具体的には kotlin.coroutines.Continuation を参照)を一致させるか、... を使用する必要があります。

    コンパイルされた正確なバイトコード シグネチャに一致する例を次に示します。

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation);
    }
    

    ... を使用した例を次に示します。

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(...);
    }
    

メンバーの仕様

クラス仕様には、保持するクラスメンバーを必要に応じて含めます。クラスに 1 つ以上のメンバーを指定した場合、ルールは他のメンバーには適用されません。

アノテーションに基づいてメンバーを指定する

アノテーションに基づいてメンバーを指定できます。クラスと同様に、アノテーションの完全修飾 Java 名の前に @ を付けます。これにより、特定のアノテーションでマークされたメンバーのみをクラス内に保持できます。たとえば、@com.example.MyAnnotation アノテーションが付けられたメソッドとフィールドを保持するには、次のようにします。

-keep class com.example.MyClass {
  @com.example.MyAnnotation <methods>;
  @com.example.MyAnnotation <fields>;
}

これをクラスレベルのアノテーション マッチングと組み合わせることで、強力なターゲット ルールを作成できます。

-keep class @com.example.ClassAnnotation * {
  @com.example.MethodAnnotation <methods>;
  @com.example.FieldAnnotation <fields>;
}

これにより、@ClassAnnotation アノテーションが付いたクラスが保持され、それらのクラスでは @MethodAnnotation アノテーションが付いたメソッドと @FieldAnnotation アノテーションが付いたフィールドが保持されます。

可能であれば、アノテーション ベースの保持ルールの使用を検討してください。このアプローチでは、コードと keep ルールの間に明示的なリンクが提供されるため、多くの場合、より堅牢な構成になります。たとえば、androidx.annotation アノテーション ライブラリはこのメカニズムを使用します。

独自のコードでリフレクションを行うには、Jetpack Keep を使用することもできます。アノテーションを対象とするキープルールを記述する代わりに、コードにアノテーションを付けると、Gradle プラグインがキープルールを生成します。

メソッド

保持ルールのメンバー仕様でメソッドを指定する構文は次のとおりです。

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

たとえば、次の keep ルールは、String を返す getUserId() という名前の public メソッドを保持します。

-keep class com.example.model.UserData {
    public java.lang.String getUserId();
}

次のように、<methods> をショートカットとして使用して、クラス内のすべてのメソッドを照合できます。

-keep class com.example.model.UserData {
    <methods>;
}

戻り値の型とパラメータの型を指定する方法については、型をご覧ください。

コンストラクタ

コンストラクタを指定するには、<init> を使用します。keep ルールのメンバー仕様でコンストラクタを指定する構文は次のとおりです。

[<access_modifier>] <init>(parameter_types);

たとえば、次の keep ルールは、リポジトリ インスタンスを受け取る UI 状態ホルダーのコンストラクタを保持します。

-keep class com.example.ui.state.UserViewModel {
    public <init>(com.example.repository.UserDataRepository);
}

すべてのパブリック コンストラクタを保持するには、次の例を参考にしてください。

-keep class com.example.ui.state.UserViewModel {
    public <init>(...);
}

フィールド

keep ルールのメンバー仕様でフィールドを指定する構文は次のとおりです。

[<access_modifier>...] [<type>] <field_name>;

たとえば、次の保持ルールは、userId という名前のプライベート文字列フィールドと、STATUS_ACTIVE という名前のパブリック静的整数フィールドを保持します。

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

次のように、<fields> をショートカットとして使用して、クラス内のすべてのフィールドを照合できます。

-keep class com.example.models.User {
    <fields>;
}

型

このセクションでは、keep ルールのメンバー仕様で戻り値の型、パラメータの型、フィールドの型を指定する方法について説明します。Kotlin ソースコードと異なる場合は、生成された Java 名を使用して型を指定してください。

プリミティブ型

プリミティブ型を指定するには、その Java キーワードを使用します。R8 は、boolean、byte、short、char、int、long、float、double のプリミティブ型を認識します。

プリミティブ型を使用したルールの例を次に示します。

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

一般的な種類

コンパイル時に、Kotlin/Java コンパイラはジェネリック型情報を消去します。そのため、ジェネリック型を含む保持ルールを記述する場合は、元のソースコードではなく、コンパイルされたコード表現をターゲットにする必要があります。ジェネリック型がどのように変更されるかについては、型消去をご覧ください。

たとえば、Box.kt で定義された、境界のないジェネリック型を含む次のコードがあるとします。

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

型消去後、T は Object に置き換えられます。クラスのコンストラクタとメソッドを保持するには、ルールで汎用 T の代わりに java.lang.Object を使用する必要があります。

保持ルールの例を次に示します。

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

NumberBox.kt に、境界付きジェネリック型を含む次のコードがある場合:

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

この場合、型消去によって T がその境界 java.lang.Number に置き換えられます。

保持ルールの例を次に示します。

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

アプリ固有の汎用型を基本クラスとして使用する場合は、基本クラスの保持ルールも追加する必要があります。

たとえば、次のコードがあるとします。

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

includedescriptorclasses でキープルールを使用すると、次のように 1 つのルールで UnpackOptions クラスと Box クラスメソッドの両方を保持できます。

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

オブジェクトのリストを処理する特定の関数を保持するには、関数のシグネチャと正確に一致するルールを記述する必要があります。ジェネリック型は消去されるため、List<Product> などのパラメータは java.util.List と見なされます。

たとえば、次のように Product オブジェクトのリストを処理する関数を含むユーティリティ クラスがあるとします。

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

次の保持ルールを使用すると、processProducts 関数のみを保護できます。

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

配列型

配列の各次元のコンポーネント タイプに [] を追加して、配列タイプを指定します。これは、クラス型とプリミティブ型の両方に適用されます。

  • 1 次元クラス配列: java.lang.String[]
  • 2 次元プリミティブ配列: int[][]

たとえば、次のコードがあるとします。

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

次の keep ルールを使用できます。

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

例

たとえば、特定のクラスとそのすべてのメンバーを保持するには、次のようにします。

-keep class com.myapp.MyClass { *; }

クラスとデフォルト コンストラクタのみを保持し、他のメンバーは保持しない場合は、次のようにします。

-keep class com.myapp.MyClass

常にメンバーを指定することをおすすめします。たとえば、次の例では、クラス MyClass 内にパブリック フィールド text とパブリック メソッド updateText() を保持します。

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

すべての公開フィールドと公開メソッドを保持するには、次の例をご覧ください。

-keep public class com.example.api.ApiClient {
    public *;
}

メンバーの指定を省略する

メンバーの指定を省略すると、R8 はクラスのデフォルト コンストラクタを保持します。

たとえば、-keep class com.example.MyClass または -keep class com.example.MyClass {} を記述すると、R8 は次のように記述したかのように扱います。

-keep class com.example.MyClass{
  void <init>();
}

メンバー名のパターンを否定する

Android Gradle プラグイン(AGP)9.2.0 以降では、キープルール内でメンバー名パターンを否定できます。これにより、パターンに基づいて保持または除外するメンバーを指定できます。パターンを否定するには、メンバー名のパターンの前に感嘆符(!)を付けます。

この機能は、より広範なパターンに一致するほとんどのメンバーを保持し、テスト専用のメソッドなど、特定のメンバーを除外する場合に使用できます。

例:

「ForTesting」で終わるメソッドを除く、com.example.MyClass 内のすべてのパブリック メソッドを保持するには、次のルールを使用します。

-keepclassmembers class com.example.MyClass {
    public *** !*ForTesting(...);
}

パッケージ レベルの関数

クラスの外部で定義された Kotlin 関数(通常はトップレベル関数と呼ばれます)を参照するには、Kotlin コンパイラによって暗黙的に追加されたクラスの生成された Java 名を使用してください。クラス名は、Kotlin ファイル名に Kt を追加したものです。たとえば、次のように定義された MyClass.kt という Kotlin ファイルがあるとします。

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

isEmailValid 関数のキープルールを作成するには、クラス仕様で生成されたクラス MyClassKt をターゲットにする必要があります。

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

ワイルドカード

次の表に、ワイルドカードを使用して、特定のパターンに一致する複数のクラスまたはメンバーに保持ルールを適用する方法を示します。

ワイルドカード クラスまたはメンバーに適用される 説明
** 両方 最も一般的に使用されます。任意の数のパッケージ区切り文字を含む、任意の型名に一致します。これは、パッケージとそのサブパッケージ内のすべてのクラスを照合する場合に便利です。
* 両方 クラス仕様の場合、パッケージ区切り文字(.)を含まない型名の任意の部分に一致します。
メンバー仕様の場合、メソッド名またはフィールド名に一致します。単独で使用する場合は、** のエイリアスでもあります。
? 両方 クラス名またはメンバー名の任意の 1 文字に一致します。
*** メンバー プリミティブ型(int など)、クラス型(java.lang.String など)、任意の次元の配列型(byte[][] など)を含む任意の型に一致します。
... メンバー メソッドのパラメータのリストに一致します。
% メンバー 任意のプリミティブ型(int、float、boolean など)に一致します。

特殊なワイルドカードの使用例をいくつかご紹介します。

  • 同じ名前で、異なるプリミティブ型を入力として受け取るメソッドが複数ある場合は、% を使用して、それらすべてを保持する keep ルールを記述できます。たとえば、次の DataStore クラスには複数の setValue メソッドがあります。

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    次のキープルールはすべてのメソッドを保持します。

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • 名前が 1 文字だけ異なる複数のクラスがある場合は、? を使用して、それらすべてを保持する keep ルールを記述します。たとえば、次のクラスがあるとします。

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    次の保持ルールは、すべてのクラスを保持します。

    -keep class com.example.models.UserV?
    
  • クラス Example と AnotherExample(ルートレベルのクラスの場合)は一致させるが、com.foo.Example は一致させない場合は、次の保持ルールを使用します。

    -keep class *Example
    
  • * を単独で使用すると、** のエイリアスとして機能します。たとえば、次の保持ルールは同等です。

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

条件付き保持ルール

標準の保持ルールのほか、特定の条件が満たされた場合にのみ適用される条件付き保持ルールを使用することもできます。条件付きルールは、-if フラグを使用して指定できます。-if フラグに続く keep ルールは、-if フラグのクラス仕様が一致する場合にのみ有効になります。

条件付き保持ルールは、リフレクションを使用するライブラリやコードパターンを扱う場合に特に便利です。この場合、保持ルールは、特定のクラスやメンバーが存在するか、パターンに一致する場合にのみ必要になります。条件付きルールを使用すると、不要なコードの保持を防ぎ、アプリのサイズを最小限に抑えることができます。

条件付き保持ルールの一般的な構文は次のとおりです。

-if <class_specification_if> <keep_rule>

-if 条件のクラス仕様にワイルドカード(* や ** など)が含まれている場合、ワイルドカードに一致する文字のシーケンスがキャプチャされます。これらのキャプチャされた文字列は、後方参照を使用して後続の保持ルールで参照できます。<1> は最初のワイルドカードでキャプチャされた文字列を参照し、<2> は 2 番目のワイルドカードでキャプチャされた文字列を参照します。

たとえば、Jetpack Navigation コンポーネントは、デスティネーション間でタイプセーフな引数を渡すための NavArgs クラスを生成します。NavArgsLazy デリゲートを使用すると、リフレクションを使用して、生成された NavArgs クラスの静的 fromBundle メソッドを検索して呼び出し、引数を逆シリアル化します。アプリで NavArgs を使用している場合は、NavArgs インターフェースを実装するクラスの fromBundle メソッドのみを保持する必要があります。

条件付き keep ルールを使用して、クラスが androidx.navigation.NavArgs を実装する場合、R8 はその特定のクラスの fromBundle メソッドを保持するように指定できます。

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

この例では、** は -if 条件の最初で唯一のワイルドカードです。androidx.navigation.NavArgs を実装するクラスのクラス名と一致します。** が一致する文字列(この場合はクラス名)がキャプチャされ、後続のルールで <1> を使用して参照できます。したがって、-keepclassmembers ルールは、-if 条件に一致した androidx.navigation.NavArgs を実装するクラスに適用されます。R8 が NavArgs を実装するクラスを見つけられない場合、この keep ルールは無視されます。

もう一つの一般的なユースケースは、Gson などの JSON シリアル化ライブラリを使用する場合です。データモデル クラスのフィールドで Gson の @SerializedName アノテーションを使用している場合は、条件付きルールを使用して、Gson がリフレクションに必要なクラスとそのメンバーを保護できます。

# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
    @com.google.gson.annotations.SerializedName <fields>;
    <init>(...);
}

後方参照は文字列をキャプチャします。ワイルドカードが名前の一部のみに一致する場合、文字列はクラス名の部分文字列になります。たとえば、-if class com.example.*X* を使用すると、R8 は X の前の部分文字列を <1> として、X の後の部分文字列を <2> としてキャプチャします。次のルールでは、これを使用して X を含むクラス名を検索し、X が Y に置き換えられた対応するクラスを保持します。

# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>

リフレクションの条件付き保持ルール

条件付きの保持ルールの一般的なユースケースは、実行時に特定のメソッドまたはクラスが動的にアクセスされるリフレクションを処理することです。たとえば、ライブラリがリフレクションを使用してコードを操作する場合、そのライブラリの特定の機能を使用するときにのみ、特定のメンバーを保持する必要があります。

Jetpack Navigation ライブラリは、NavArgsLazy デリゲートを使用してリフレクションを行い、生成された NavArgs クラスの静的 fromBundle メソッドを呼び出して、型安全な引数渡しを行います。このメソッドがすべてのクラスではなく NavArgs 実装でのみ保持されるように、Jetpack Navigation には次の条件付き保持ルールが含まれています。

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

このルールでは、 が必要なクラスでのみ fromBundle が保持されます。 すべてのクラスで保持したり、 必要なクラスを手動で指定したりする必要はありません。

生成された Java 名を検査する

キープルールを記述する際は、Java バイトコードにコンパイルされた後の名前を使用して、クラスやその他の参照型を指定する必要があります(例については、クラスの指定と型をご覧ください)。コードの生成された Java 名を確認するには、Android Studio で次のいずれかのツールを使用します。

  • APK Analyzer
  • Kotlin ソースファイルを開いた状態で、[Tools] > [Kotlin] > [Show Kotlin Bytecode] > [Decompile] に移動して、バイトコードを調べます。