[Android 개념] FragmentManager

이창민·2022년 8월 8일
0

Android 개념

목록 보기
6/9

현재 기준 안드로이드 공식 문서에서는
Jetpack Navigation 사용을 권장합니다. 저도 권장합니다..

본 글을 쓴 이유는
어플리케이션에서 화면 회전 혹은 중단 상태에 있는 액티비티가 메모리 회수가 된 경우 FragmentManager는 어떻게 fragment 리스트를 가지고 다시 복구할까?

가 궁금하여 알아보기 위해 글을 썼습니다.

Fragment Manager

FragmentManager 는 프래그먼트 리스트와 프래그먼트 트랜잭션의 백 스택을 처리합니다.

FragmentManager 는 프래그먼트의 뷰를 액티비티의 뷰 계층에 추가하고 프래그먼트의 생명주기를 주도하는 책임을 갖습니다.

fragment가 있는 어플리케이션을 개발하면 activity는 fragment를 호스팅하게 됩니다.
위 그림같이 호스팅 액티비티 위에 호스팅 프래그먼트가 있으면 그 안에 하위(자식) 프래그먼트들이 배치되는 것을 확인할 수 있습니다.

그리고 이들은 각각의 프래그먼트 매니저들이 관리합니다.

위 그림을 보면 하위 프래그먼트를 관리하는 FragmentManager가 각 호스트에 연결됩니다.

Using Fragment Manager

FragmentManager는 프래그먼트 백 스택을 관리합니다.

런타임 시 FragmentManager는 사용자와의 상호작용에 응답해 프래그먼트를 추가하거나 삭제하는 등 백스택 작업을 실행할 수 있습니다.

각 프래그먼트의 추가, 삭제는 FragmentTransaction이라는 단일 단위로 커밋 됩니다.

예를 들어, 사용자의 기기에서 back 버튼을 누르는 경우
혹은 fragmentManager.popBackStack()이 호출되는 경우,
최상위 프래그먼트 트랜잭션이 스택에서 사라집니다.

FragmentTransaction

런타임시 FragmentManager에 사용자 응답으로 Fragment를 추가, 제거, 교체 등 작업을 수행할 수 있습니다. 이런 변경의 집합을 FragmentTransaction이라는 단일 단위로 커밋됩니다.

하나의 트랜잭션이 다수의 오퍼레이션을 포함하면 해당 트랜잭션이 백 스택에서 제거될 때,
이 오퍼레이션들은 역으로 실행됩니다.

FragmentTransaction 클래스는 fluent interface를 사용합니다.

fluent interface는 코드를 이해하기 쉽게 하는 객체지향 기법입니다.
일반적으로 함수의 연쇄 호출 형태로 구현됩니다.

즉, FragmentTransaction을 구성하는 함수들은 Unit 대신 FragmentTransaction을 반환해 이를 계속 이어서 호출할 수 있습니다.

Fragment list의 보존

아래의 코드가 있다고 가정해보자.

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

profile
android 를 공부해보아요

0개의 댓글