현재 기준 안드로이드 공식 문서에서는
Jetpack Navigation 사용을 권장합니다.저도 권장합니다..
본 글을 쓴 이유는
어플리케이션에서 화면 회전 혹은 중단 상태에 있는 액티비티가 메모리 회수가 된 경우 FragmentManager는 어떻게 fragment 리스트를 가지고 다시 복구할까?
가 궁금하여 알아보기 위해 글을 썼습니다.
FragmentManager
는 프래그먼트 리스트와 프래그먼트 트랜잭션의 백 스택을 처리합니다.
FragmentManager
는 프래그먼트의 뷰를 액티비티의 뷰 계층에 추가하고 프래그먼트의 생명주기를 주도하는 책임을 갖습니다.
fragment가 있는 어플리케이션을 개발하면 activity는 fragment를 호스팅하게 됩니다.
위 그림같이 호스팅 액티비티 위에 호스팅 프래그먼트가 있으면 그 안에 하위(자식) 프래그먼트들이 배치되는 것을 확인할 수 있습니다.
그리고 이들은 각각의 프래그먼트 매니저들이 관리합니다.
위 그림을 보면 하위 프래그먼트를 관리하는 FragmentManager
가 각 호스트에 연결됩니다.
FragmentManager
는 프래그먼트 백 스택을 관리합니다.
런타임 시 FragmentManager
는 사용자와의 상호작용에 응답해 프래그먼트를 추가하거나 삭제하는 등 백스택 작업을 실행할 수 있습니다.
각 프래그먼트의 추가, 삭제는 FragmentTransaction
이라는 단일 단위로 커밋 됩니다.
예를 들어, 사용자의 기기에서 back 버튼을 누르는 경우
혹은fragmentManager.popBackStack()
이 호출되는 경우,
최상위 프래그먼트 트랜잭션이 스택에서 사라집니다.
런타임시 FragmentManager
에 사용자 응답으로 Fragment
를 추가, 제거, 교체 등 작업을 수행할 수 있습니다. 이런 변경의 집합을 FragmentTransaction
이라는 단일 단위로 커밋됩니다.
하나의 트랜잭션이 다수의 오퍼레이션을 포함하면 해당 트랜잭션이 백 스택에서 제거될 때,
이 오퍼레이션들은 역으로 실행됩니다.
FragmentTransaction
클래스는 fluent interface
를 사용합니다.
fluent interface는 코드를 이해하기 쉽게 하는 객체지향 기법입니다.
일반적으로 함수의 연쇄 호출 형태로 구현됩니다.
즉, FragmentTransaction을 구성하는 함수들은 Unit 대신 FragmentTransaction을 반환해 이를 계속 이어서 호출할 수 있습니다.
아래의 코드가 있다고 가정해보자.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val currentFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container)
if (currentFragment == null) {
val fragment = CrimeListFragment.newInstance()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit()
}
}
우선 R.id.fragment_container
의 컨테이너 뷰 ID와 연관된 프래그먼트를 FragmentManager
에 요청합니다.
만약 해당 프래그먼트가 리스트에 있다면 FragmentManager는 해당 프래그먼트를 반환할 것입니다.
여기서 궁금한 점이 발생합니다.
Activity의 onCreate
시점에서 어떻게 Fragment가 리스트에 존재할 수 있는가..
여러 이유가 있을 수 있습니다.
장치가 회전되는 상황, 안드로이드 OS의 메모리 회수로 인해 MainActivity가 소멸되었다가 다시 생성되는 상황..
어쨋거나 이런 상황에서 onDestroy
가 호출된 이후고 어떻게 FragmentManager는 연관된 프래그먼트 리스트를 보존해 액티비티의 onCreate
가 호출될 때, 새로운 FragmentManager
인스턴스가 해당 리스트를 가져와 프래그먼트를 다시 생성해 이전 상태로 복원합니다.
그렇다면.. FragmentManager는 어디에 저장된 프래그먼트 리스트를 어떻게 가져오는지 궁금해집니다..
우선 FragmentManager.findFragmentByID(...)
의 내부 코드는 아래와 같습니다.
/**
* Finds a fragment that was identified by the given id either when inflated
* from XML or as the container ID when added in a transaction. This first
* searches through fragments that are currently added to the manager's
* activity; if no such fragment is found, then all fragments currently
* on the back stack associated with this ID are searched.
* @return The fragment if found or null otherwise.
*/
@Nullable
public Fragment findFragmentById(@IdRes int id) {
return mFragmentStore.findFragmentById(id);
}
주석 내용
xml로 인플레이트될 때의 id 혹 transaction에 더해질 때의 container id 로 식별되는 fragment를 찾습니다.
Manager's activity(제 생각으로는 현재 실행중인 액티비티)에 현재 더해진 프래그먼트를 검색합니다.
만약 위 검색 결과가 없다면, 백스택에서 ID와 관련된 모든 프래그먼트들을 검색합니다.
검색결과가 있다면 fragment를 반환하고 없다면 null을 반환합니다.
주석을 읽어보면 findFragmentById의 결과가 null이 될 수 있는 것도 알 수 있고
현재 프래그먼트들은 백스택에 저장된다는 것을 알 수 있습니다.
그렇지만.. 여전히 의문을 품을 수 있습니다.
그렇다면 내부 코드의 FragmentStore
을 살펴봅시다..
class FragmentStore {
private static final String TAG = FragmentManager.TAG;
private final ArrayList<Fragment> mAdded = new ArrayList<>();
private final HashMap<String, FragmentStateManager> mActive = new HashMap<>();
private FragmentManagerViewModel mNonConfig;
...
뭔가 바로 묵은 통증이 싹 내려가는 느낌입니다.
갑자기 FragmentMagenrViewModel
이 나타납니다.
activity의 ViewModel
은 메모리 캐싱을 처리하는 것으로 알 고 있스니.. 여기서 생각해보면 FragmentManger의 캐싱을 담당할 것이라는 생각이 들어 FragmentmanagerViewModel
을 살펴보겠습니다.
final class FragmentManagerViewModel extends ViewModel {
private static final String TAG = FragmentManager.TAG;
private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
@NonNull
@Override
@SuppressWarnings("unchecked")
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
FragmentManagerViewModel viewModel = new FragmentManagerViewModel(true);
return (T) viewModel;
}
};
@NonNull
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}
private final HashMap<String, Fragment> mRetainedFragments = new HashMap<>();
private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
private final boolean mStateAutomaticallySaved;
// Only used when mStateAutomaticallySaved is true
private boolean mHasBeenCleared = false;
// Only used when mStateAutomaticallySaved is false
private boolean mHasSavedSnapshot = false;
// ...
}
살펴보면 mRetainedFragments
변수가 존재하는 것을 확인할 수 있습니다..!
FragmentManagerViewModel에서 fragment list를 가지고 있다가
activity가 사라져 fragmentManager가 없어지는 경우에도 fragmentMagenarViewModel에서 fragment list를 가지고 있다는 것을 알 수 있습니다!
긴 글 읽어주셔서 감사합니다. 고생하셨습니다.
혹시나 틀린 부분이 있다면 댓글달아주시면 감사하겠습니다.
https://developer.android.com/guide/fragments/fragmentmanager
https://developer.android.com/guide/fragments/transactions