이 가이드에서는 프래그먼트를 디버그하는 데 사용할 수 있는 도구를 설명합니다.
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
Jetpack Fragment 라이브러리 버전 1.4.0 이상에는 프래그먼트용 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) ... } }
자바
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
문서를 참고하세요.
세 가지 페널티 유형이 있습니다.
penaltyLog()
는 위반 세부정보를 Logcat에 덤프합니다.penaltyDeath()
는 위반을 감지한 경우 앱을 종료합니다.penaltyListener()
를 사용하면 위반이 감지될 때마다 호출되는 맞춤 리스너를 추가할 수 있습니다.
Policy
에서 페널티를 원하는 대로 조합하여 적용할 수 있습니다. 정책에서 페널티를 명시적으로 지정하지 않으면 기본값 penaltyLog()
가 적용됩니다. 맞춤 Policy
에서 penaltyLog()
외의 페널티를 적용하면 명시적으로 설정하지 않는 한 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() } }
자바
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
을 발생시킵니다.
이 위반은 Fragment
인스턴스를 FragmentManager
에서 삭제한 후에 재사용했음을 나타냅니다. 이러한 재사용으로 인해 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
을 발생시킵니다.
이 위반은 FragmentContainerView
이외의 컨테이너에 Fragment
를 추가했음을 나타냅니다. Fragment
태그 사용과 마찬가지로 프래그먼트 트랜잭션은 FragmentContainerView
내에서 호스팅하지 않으면 예상대로 작동하지 않을 수 있습니다. 또한 컨테이너 뷰를 사용하면 종료 애니메이션을 사용하는 프래그먼트가 다른 모든 프래그먼트 위에 그려지도록 하는 View
API의 문제를 해결하는 데 도움이 됩니다.