티스토리 뷰
Fragment
앱 UI의 재사용 가능한 부분을 나타낸다.(UI 조각) fragment는 자체 레이아웃을 정의 및 관리하고 자체 수명 주기를 보유하며 자체 입력 이벤트를 처리할 수 있다. fragment는 단독으로 실행될 수 없다.
FragmentManager
fragment의 추가, 제거, 교체, back stack, lifecycle, 상태 저장/복원 등을 관리하는 관리자 객체이다.
- supportFragmentManager: AppCompatActivity의 fragment를 관리한다.
- childFragmentManager: 해당 fragment 내의 fragment를 관리한다. 해당 fragment는 관리하지 않는다.
- parentFragmentManager: 자기자신 fragment를 포함한 직계 부모 fragment를 관리한다.
Activity
└── FragmentController
└── FragmentManager
├── FragmentStore
├── BackStack
├── FragmentStateManager
└── SpecialEffectsController
FragmentTransaction
FragmentManager는 직접 변경하지 않고 beginTransaction()으로 transaction을 생성한다.
supportFragmentManager.beginTransaction()
.replace(...)
.addToBackStack(null)
.commit()
// DSL
supportFragmentManager.commit {
replace(...)
}
commit() VS commitNow()
- commit(): transaction을 즉시 실행하지 않고 Main Thread queue에 예약한 후 순차적으로 동작하므로 바로 화면에 적용이 되지 않을 수 있다. 이 시점엔 아직 실행이 안됐을 수 있어서 fragment가 null일 수 있다.
- commitNow(): transaction을 즉시 실행한다. 따라서 호출이 끝난 시점에는 Fragment transaction이 이미 적용된 상태이다.
commitNow()를 함부로 쓰면 위험하다.
class FragmentA : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
parentFragmentManager.commitNow {
replace(...)
}
}
}
위 코드처럼 FragmentA lifecycle 처리 도중 commitNow()를 호출하면, FragmentB의 lifecycle이 즉시 실행될 수 있다.
즉 FragmentManager가 현재 lifecycle/state를 처리하는 도중 다른 transaction이 재진입(reentrant) 실행될 수 있으며, 이는 lifecycle ordering이나 state consistency 문제를 유발할 수 있다.
또한 commitNow()는 addToBackStack()과 함께 사용할 수 없다. BackStack은 FragmentManager가 transaction 순서와 상태를 scheduling 기반으로 관리해야 하는데, commitNow()는 transaction queue를 우회해 즉시 실행되기 때문이다.
따라서 commitNow()에서 addToBackStack()을 호출하면 IllegalStateException이 발생한다.
프래그먼트 트랜잭션 | Android Developers
setReorderingAllowed()
FragmentManager가 transaction 내부 opreration들의 실행 순서를 재배치(reorder)해서 최종 상태 기준으로 최적화할 수 있게 허용해주는 옵션이다.
- false일 때
- A 제거
- A lifecycle down
- A view destroy
- B 추가
- B lifecycle up
- B view create
- true일 때
- A 제거 + B 추가를 묶어서 처리
FragmentManager 내부에는 remove(), add(), show(), hide() 등 transaction operation list가 있다. 이걸 최적화 가능한 순서로 재배열해줄 수 있다는 의미이다.
그렇다고 lifecycle을 건너뛴다는 의미가 아니다. 단지 불필요한 상태 전환을 줄이는 것이다.
대부분은 true를 사용하고 false는 특정 lifecycle을 디버깅하는 상황에 사용하지 않을까 싶다.
화면 회전 시 Fragment의 상태
fragment는 화면회전시 activity가 파괴되기 전에 fragment의 상태를 FragmentManagerState로 저장한다. 이건 activity의 savedInstance와 연결되어 재생성시 상태를 복원할 수 있게한다. 하지만 프로세스가 죽거나 activity가 finish()되면 저장된 값이 사라진다.
finish()와 다르게 화면회전은 fragment의 상태를 보관하는 이유
시스템은 화면회전시 다시 actvity를 생성하기를 기대하기 때문에 FragmentManager가 자동으로 상태를 보관한다.
1. 상태 저장 요청
시스템이 먼저 Activity.onSaveInstanceState()를 호출한다. 여기서 FragmentManager, View hierarchy, SavedStateRegistry 등이 상태를 저장한다.
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getAutofillClientController().onSaveInstanceState(outState);
dispatchActivitySaveInstanceState(outState);
}
즉, FragmentManager도 여기서 현재 fragment의 상태 snapshot을 Bundle에 기록한다.(Activity.onSaveInstanceState()에서 Bundle 객체에 상태를 저장한다.)
2. Activity destroy
그 다음 Activity가 파괴된다.
3. 새 Activity 생성
onCreate(savedInstanceState)
onCreate가 호출되며 여기서 savedInstanceState 안에 FragmentManager가 저장한 상태가 들어있다.
4. FragmentManager를 통한 자동 복원
void restoreSaveStateInternal(@Nullable Parcelable state) {...}
다음 함수를 통해 저장된 fragment를 복원한다.
State Loss란
Activity/Fragment 상태가 이미 저장된 이후 발생한 FragmentTransaction이 저장 snapshot에 반영되지 않아, 이후 복원 시 해당 상태 변경이 유실되는 현상이다.
Android는 위에서 설명한 것 처럼 화면 회전이나 프로세스 종료 상황에 대비하기 위해 onSaveInstanceState() 시점에 현재 UI 상태를 저장한다.
즉 시스템은 이 시점을 기준으로 현재 UI 상태 저장을 완료한다. 그런데 이후에 FragmentTransaction이 발생하면 실제 화면 상태와 저장된 snapshot의 상태가 달라진다.
예를 들면 onSaveInstanceState 시점에 FragmentA를 저장하고, 이후에
supportFragmentManager.commit {
replace(R.id.container, FragmentB())
}
다음 코드를 수행하면 실제 화면은 FragmentB가 되지만, 저장된 상태에는 FragmentA만 기록 되어있다.
따라서 방금 수행한 FragmentTransaction이 복원과정에서 사라지게 되는데 이것이 State Loss이다.
FragmentManager는 State Loss를 막기 위해 상태 저장 이후 transaction 수행 시 다음과 같이 예외를 발생시킨다.
IllegalStateException:
Can not perform this action after onSaveInstanceState
이 예외를 해결하는 방법들은 다음과 같다.
commitAllowingStateLoss()
상태 유실 가능성을 감수하고 transaction을 수행하는 함수이다. transaction은 실행되지만 이후 복원 과정에서 해당 상태가 사라질 수 있기 때문에 지양하는게 좋을 듯하다.
isStateSaved
onSaveInstanceState 이후에는 transaction을 수행하지 않는 것이다.
if (!supportFragmentManager.isStateSaved) {
supportFragmentManager.commit {
replace(...)
}
}
다음과 같이 현재 state가 저장되지 않았을 때만 transaction을 수행한다.
'안드로이드' 카테고리의 다른 글
| 다양한 화면 크기에 대응하기 (0) | 2026.06.02 |
|---|---|
| OpenGL VS YUV Buffer 조작 (1) | 2026.03.03 |
| CameraX VS Camera2 (0) | 2026.02.22 |
| Coil (0) | 2026.01.04 |
| XML VS Jetpack Compose (0) | 2026.01.01 |
