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

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

FragmentManager ロギング

FragmentManager は、さまざまなメッセージを 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 インスタンスのフラグメント マネージャーの一意の ID。これは、構成の変更や、プロセスの終了、再作成の際も安定しています。
  • Fragment が追加されたコンテナの ID(設定されている場合のみ)。
  • Fragment タグ(設定されている場合のみ)。

以下に、DEBUG ログエントリの例を示します。

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • Fragment クラスは NavHostFragment
  • ID ハッシュコードは 92d8f1d
  • 一意の ID は fd92599e-c349-4660-b2d6-0ece9ec72f7b
  • コンテナ ID は 0x7f080116
  • タグは設定されていないため、省略されています。存在する場合、tag=tag_value 形式で ID の後に続きます。

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

NavHostFragment が初期化され、FirstFragment 型の startDestination Fragment が作成されて、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 が含まれています。これにより、アプリに予期せぬ動作を引き起こす一般的な問題を検出できます。StrictMode の操作の詳細については、StrictMode をご覧ください。

カスタム 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());
        ...
   }
}

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

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

起こり得る違反をすべて検出するために StrictMode を構成できる最新のポイントは、super.onCreate() の呼び出し前の 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 がスローされます。

この違反は、特に setRetainInstance() または getRetainInstance()(ともに非推奨)への呼び出しがある場合に、保持されている Fragment の使用を示すものです。

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

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

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

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

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

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

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

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

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

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

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