フラグメントをデバッグする

このガイドでは、フラグメントのデバッグに使用できるツールについて説明します。

FragmentManager ロギング

FragmentManager は、さまざまなメッセージを Logcat に出力できます。これは、Logcat にノイズが加わらないようにデフォルトで無効になっていますが、これらのログメッセージがフラグメントのトラブルシューティングに役立つこともあります。FragmentManager は、Debug と Verboseログレベルで最も有用な出力を行います。

ロギングを有効にするには、adb shell コマンドを使用します。

adb shell setprop log.tag.FragmentManager DEBUG

または、詳細ログを有効にできます。

adb shell setprop log.tag.FragmentManager VERBOSE

詳細ログを有効にすると、[Logcat] ウィンドウでログレベルのフィルタを適用できます。ただし、これは FragmentManager のログだけでなく、すべてのログをフィルタします。通常は、必要なログレベルでのみ FragmentManager ロギングを有効にすることをおすすめします。

DEBUG ロギング

DEBUG レベルで、FragmentManager は通常、ライフサイクルの状態変化に関連するログメッセージを出力します。各ログエントリには、Fragment からの toString() ダンプが含まれています。ログエントリは次の情報で構成されます。

  • Fragment インスタンスの単純なクラス名。
  • Fragment インスタンスの ID ハッシュコード
  • Fragment インスタンスの、FragmentManager の一意の ID。これは、構成の変更や、プロセスの終了、再作成の際も安定しています。
  • Fragment が追加されたコンテナの ID(設定されている場合のみ)。
  • Fragment タグ(設定されている場合のみ)。
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)

この Fragment は次のように識別できます。

  • Fragment クラスは NavHostFragment.
  • ID ハッシュコードは 92d8f1d
  • 一意の ID は fd92599e-c349-4660-b2d6-0ece9ec72f7b
  • コンテナ ID は 0x7f080116
  • タグは設定されていないため、省略されています。存在していれば、ID の後に tag=tag_value の形式で続きます。

わかりやすくするために、次の例では UUID を短縮しています。

ここでは、NavHostFragment が初期化され、FirstFragment 型の startDestinationFragment が作成されて、RESUMED 状態に移行しています。

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: SET_PRIMARY_NAV NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)

ユーザーによる操作の後、FirstFragment はライフサイクルのさまざまな状態から移行しています。その後、SecondFragment がインスタンス化され、RESUMED 状態に移行します。

D/FragmentManager:   mName=07c8a5e8-54a3-4e21-b2cc-c8efc37c4cf5 mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: SecondFragment{84132db} (<UUID> id=0x7f080116)

同じ Fragment クラスのさまざまなインスタンスをトラッキングできるように、すべての Fragment インスタンスに識別子が付けられています。

VERBOSE ロギング

VERBOSE レベルで、FragmentManager は通常、内部状態に関するログメッセージを出力します。

V/FragmentManager: Run: BackStackEntry{f9d3ff3}
V/FragmentManager: add: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{5cfd2ae}
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: SET_PRIMARY_NAV NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{e93833f}
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{e93833f}
V/FragmentManager: add: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment FirstFragment{886440c} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{7578ffa V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.constraintlayout.widget.ConstraintLayout{3968808 I.E...... ......I. 0,0-0,0} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{2ba8ba1 V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.fragment.app.FragmentContainerView{7578ffa I.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{5cfd2ae}
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)

これは、FirstFragment での読み込みのみを対象としています。SecondFragment への移行を含めると、ログエントリが大幅に増加します。VERBOSE レベルのログメッセージの大部分は、アプリ デベロッパーにとってほとんど役に立ちません。ただし、バックスタックの変更がいつ行われたのかを確認することは、問題のデバッグに役立つことがあります。

フラグメントの StrictMode

バージョン 1.4.0 以降の Jetpack Fragment ライブラリには、フラグメントの StrictMode が含まれています。これにより、アプリが予期しない動作をする原因となる一般的な問題を捕捉できます。

デフォルトでは、Fragment StrictMode には何も捕捉しない LAX ポリシーがあります。ただし、カスタム ポリシーを作成することは可能です。カスタム Policy は、検出する違反を定義し、違反を検出した場合に適用するペナルティを指定します。

カスタムの StrictMode ポリシーを適用するには、FragmentManager に割り当てます。これは、できるだけ早い段階で行う必要があります。ここでは、init ブロックまたは Java コンストラクタで行います。

Kotlin

class ExampleActivity : AppCompatActivity() {

    init {
        supportFragmentManager.strictModePolicy =
            FragmentStrictMode.Policy.Builder()
                .penaltyDeath()
                .detectFragmentReuse()
                .allowViolation(FirstFragment::class.java,
                                FragmentReuseViolation::class.java)
                .build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    ExampleActivity() {
        getSupportFragmentManager().setStrictModePolicy(
                new FragmentStrictMode.Policy.Builder()
                        .penaltyDeath()
                        .detectFragmentReuse()
                        .allowViolation(FirstFragment.class,
                                        FragmentReuseViolation.class)
                        .build()
        );
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding =
            ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

たとえばブール値リソースの値から、厳格モードを有効にするかどうかを判断するために Context を確認する必要がある場合は、OnContextAvailableListener を使用して、FragmentManager への StrictMode ポリシーの割り当てを延期できます。

Kotlin

class ExampleActivity : AppCompatActivity() {

    init {
        addOnContextAvailableListener { context ->
            if(context.resources.getBoolean(R.bool.enable_strict_mode)) {
                supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder()
                    .penaltyDeath()
                    .detectFragmentReuse()
                    .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java)
                    .build()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    ExampleActivity() {
        addOnContextAvailableListener((context) -> {
            if(context.getResources().getBoolean(R.bool.enable_strict_mode)) {
                getSupportFragmentManager().setStrictModePolicy(
                        new FragmentStrictMode.Policy.Builder()
                                .penaltyDeath()
                                .detectFragmentReuse()
                                .allowViolation(FirstFragment.class, FragmentReuseViolation.class)
                                .build()
                );
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

起こり得る違反をすべて捕捉するために厳格モードを設定する必要がある最新のポイントは onCreate() ですが、ここでは super.onCreate() の呼び出しの前に厳格モードを設定する必要があります。

Kotlin

class ExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder()
            .penaltyDeath()
            .detectFragmentReuse()
            .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java)
            .build()

        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getSupportFragmentManager().setStrictModePolicy(
                new FragmentStrictMode.Policy.Builder()
                        .penaltyDeath()
                        .detectFragmentReuse()
                        .allowViolation(FirstFragment.class, FragmentReuseViolation.class)
                        .build()
                );

        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

この例で使用されているポリシーは、フラグメントの再利用違反のみを検出し、違反が発生するたびにアプリを終了します。penaltyDeath() は、違反を無視できないほど早く失敗するため、デバッグビルドに役立ちます。

特定の違反を選択的に許可することもできます。ただしこの例ではポリシーによって、他のすべてのフラグメント タイプに対して、この違反が適用されます。これは、サードパーティのライブラリ コンポーネントに StrictMode 違反が含まれている可能性がある場合に便利です。このような場合、ライブラリで違反が修正されるまで、所有していないコンポーネントの StrictMode の許可リストにこれらの違反を一時的に追加できます。

その他の違反の設定方法については、FragmentStrictMode.Policy.Builder のドキュメントをご覧ください。

ペナルティは 3 種類あります。

  • penaltyLog() は、違反の詳細を LogCat にダンプします。
  • penaltyDeath() は、違反が検出されたときにアプリを終了します。
  • penaltyListener() により、違反が検出されるたびに呼び出されるカスタム リスナーを追加できます。

Policy ではペナルティを自由に組み合わせて適用できます。ポリシーにペナルティが明示的に指定されていない場合は、デフォルトの penaltyLog() が適用されます。カスタム PolicypenaltyLog() 以外のペナルティを適用した場合、明示的に設定しない限り penaltyLog() は無効になります。

penaltyListener() は、違反をログに記録するサードパーティのロギング ライブラリがある場合に便利です。あるいは、致命的でない違反の捕捉をリリースビルドで有効にし、クラッシュ レポート ライブラリにログを記録することもできます。この戦略では野放しの違反を検出できます。

グローバル StrictMode ポリシーを設定するには、FragmentStrictMode.setDefaultPolicy() メソッドを介してすべての FragmentManager インスタンスに適用されるデフォルト ポリシーを設定することをおすすめします。

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        FragmentStrictMode.defaultPolicy =
            FragmentStrictMode.Policy.Builder()
                .detectFragmentReuse()
                .detectFragmentTagUsage()
                .detectRetainInstanceUsage()
                .detectSetUserVisibleHint()
                .detectTargetFragmentUsage()
                .detectWrongFragmentContainer()
                .apply {
                    if (BuildConfig.DEBUG) {
                        // Fail early on DEBUG builds
                        penaltyDeath()
                    } else {
                        // Log to Crashlytics on RELEASE builds
                        penaltyListener {
                            FirebaseCrashlytics.getInstance().recordException(it)
                        }
                    }
                }
                .build()
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        FragmentStrictMode.Policy.Builder builder = new FragmentStrictMode.Policy.Builder();
        builder.detectFragmentReuse()
                .detectFragmentTagUsage()
                .detectRetainInstanceUsage()
                .detectSetUserVisibleHint()
                .detectTargetFragmentUsage()
                .detectWrongFragmentContainer();
        if (BuildConfig.DEBUG) {
            // Fail early on DEBUG builds
            builder.penaltyDeath();
        } else {
            // Log to Crashlytics on RELEASE builds
            builder.penaltyListener((exception) ->
                    FirebaseCrashlytics.getInstance().recordException(exception)
            );
        }
        FragmentStrictMode.setDefaultPolicy(builder.build());
    }
}

以降のセクションでは、違反の種類と考えられる回避策について説明します。

フラグメントの再利用

detectFragmentReuse() を使用するとフラグメントの再利用違反が有効になり、FragmentReuseViolation がスローされます。

この違反は、FragmentManager から削除された後に Fragment インスタンスが再利用されたことを示します。この再利用は、Fragment が以前の使用からの状態を保持して動作が一貫しないために、問題を引き起こす可能性があります。毎回新しいインスタンスを作成すれば、FragmentManager に追加された時点で常に初期状態になります。

フラグメント タグの使用

detectFragmentTagUsage() を使用するとフラグメント タグの使用違反が有効になり、FragmentTagUsageViolation がスローされます。

この違反は、XML レイアウトで <fragment> タグを使用して Fragment がインフレートされたことを示します。この問題を解決するには、<fragment> タグではなく、<androidx.fragment.app.FragmentContainerView> 内で Fragment をインフレートします。FragmentContainerView を使用してインフレートしたフラグメントは、Fragment トランザクションと構成変更を確実に処理できます。代わりに <fragment> タグを使用した場合、想定どおりに動作しない可能性があります。

保持インスタンスの使用

detectRetainInstanceUsage() を使用すると保持インスタンスの使用違反が有効になり、RetainInstanceUsageViolation がスローされます。

この違反は、保持されている Fragment を使用していることを示します。具体的には、setRetainInstance() または getRetainInstance() の呼び出しがある場合です(どちらもサポートが終了しています)。

これらのメソッドを使用して保持された Fragment インスタンスを自分で管理するのではなく、これを処理する ViewModel に状態を保存する必要があります。

ユーザー表示ヒントの設定

detectSetUserVisibleHint() を使用するとユーザー表示ヒントの設定違反が有効になり、SetUserVisibleHintViolation がスローされます。

この違反は、サポートが終了した setUserVisibleHint() を呼び出していることを示します。

このメソッドを手動で呼び出している場合は、代わりに setMaxLifecycle() を呼び出す必要があります。このメソッドをオーバーライドする場合、true を渡すと onResume() に、false を渡すと onPause() に、動作を移す必要があります。

ターゲット フラグメントの使用

detectTargetFragmentUsage() を使用するとターゲット フラグメントの使用違反が有効になり、TargetFragmentUsageViolation がスローされます。

この違反は、サポートが終了した setTargetFragment()getTargetFragment()getTargetRequestCode() を呼び出していることを示します。これらのメソッドを使用する代わりに、FragmentResultListener を登録する必要があります。詳細については、フラグメント間で結果を受け渡すをご覧ください。

フラグメント コンテナの誤り

detectWrongFragmentContainer() を使用するとフラグメント コンテナの誤り違反が有効になり、WrongFragmentContainerViolation がスローされます。

この違反は、FragmentFragmentContainerView 以外のコンテナに追加していることを示します。フラグメント タグの使用と同様に、FragmentContainerView 内でホストされている場合を除き、Fragment トランザクションが想定どおりに動作しない可能性があります。また、終了アニメーションを使用するフラグメントが他のすべてのフラグメントの上に描画されるという、View API の問題にも対処できます。