ViewModelProvider, ViewModelStore

DoDoBest

·

2024. 3. 29. 23:11

오늘은 Pluu 개발자 님의 ViewModelProvider 글을 읽고 정리했습니다.

https://pluu.github.io/blog/android/2020/05/04/viewmodel-b-to-d/

 

Pluu Dev - ViewModel의 B에서 D까지

[DataBinding] 중복으로 BindingAdapter가 생성되는 문제 코드 Posted on 23 Mar 2024 UI Code Snippet용 Plugin 제작기 ~ 4부 : Drag, Copy, Paste Posted on 17 Mar 2024 UI Code Snippet용 Plugin 제작기 ~ 3부 : Import Posted on 09 Mar 2024 UI

pluu.github.io

 

ViewModelProvider

activity-ktx 라이브러리에 있는 by viewModels도 lazy하게 ViewModel을 생성한다. android framework 구현 코드를 확인해보니 Lazy로 warpping하여 viewModel을 반환하고 있었다.

https://android.googlesource.com/platform/frameworks/support/+/androidx-main/activity/activity/src/main/java/androidx/activity/ActivityViewModelLazy.kt

 

 

 

ViewModelProvider(this) 에서 this로 전달하는 파라미터는 ViewModelStoreOwner 인터페이스 구현체이며, ComponentActivity, Fragment는 ViewModelStoreOwner를 구현하는 클래스다.

또한 ComponentActivity, Fragment를 상속하는 클래스도 ViewModelStoreOwner 인터페이스의 성격을 가진다.

 

 

 

ViewModelProvider는 owner가 가지고 있는 ViewModelProvider.Factory 또는 파라미터로 전달한 Factory를 mFactory 변수에 저장한다.

 

class ViewModelProvider {
  public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { // 생성자 패턴 1
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
          ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
          : NewInstanceFactory.getInstance());
  }

  public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { // 생성자 패턴 2
    this(owner.getViewModelStore(), factory);
  }

  public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { // 생성자 패턴 3
    mFactory = factory;
    mViewModelStore = store;
  }
  ...
}

 

ViewModelProvider.get

이후 ViewModelProvider의 get 함수를 이용해서 ViewModel 인스턴스를 가져온다.

 

class ViewModelProvider {
  ...  
  @NonNull
  @MainThread
  public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);
    
    if (modelClass.isInstance(viewModel)) {
      if (mFactory instanceof OnRequeryFactory) {
        ((OnRequeryFactory) mFactory).onRequery(viewModel);
      }
      return (T) viewModel;
    } else {
      ...
    }
    if (mFactory instanceof KeyedFactory) {
      viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
      viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
  }
  ...
}

 

ViewModelStore에 생성된 인스턴스가 이미 있으면 해당 인스턴스를 반환하고, 없으면 mFactory로 ViewModel을 생성해서 반환한다.

viewModelStore는 ViewModel을 임시 저장하는 Store 클래스다.

 

ViewModelProvider.Factory

ViewModelProvider.Factory는 새로운 ViewModel 인스턴스를 만드는 역할을 정의한 인터페이스다.

 

 

ViewModel 생성과정

  1. ViewModelProvider 객체 생성
  2. ViewModelProvider#get 을 통해 ViewModel 요청
  3. ViewModelStore에 없을 경우 ViewModelProvider.Factory을 통해서 객체 생성 후 반환
  4. ViewModelStore에 인스턴스화한 ViewModel을 저장

 

ViewModel이 사라지는 시점

ViewModel의 owner 생명주기가 완료되면 리소스 정리를 위해 ViewModel.onCleared가 호출되며, 이때 ViewModel이 제거된다. 정확히는, ViewModelStore가 hashMap형태로 관리하고 있던 ViewModel에 대한 객체 참조가 없어짐으로써 GC에 의해 메모리에서 회수될 가능성이 생기는 것이다.

 

예를 들어, Activity에서 onCreate 시점과 onDestroy 시점에 ViewModelStore의 clear 함수를 호출하면 어떻게 될까?

 

 

 

ViewModelStore의 상태를 확인하기 위해 ViewModelStore clear 함수에 디버깅 포인트를 찍고 확인해봤다.

 

 

 

1.

앱이 처음 실행되고, Activity의 onCreate에서 호출되는 시점에 ViewModelStore에는 FragmentManagerViewModel만 존재한다.

앞서 by viewModels는 lazy하게 생성된다는 것을 확인했고, 아직 ViewModel에 접근하는 코드가 없었기 때문에 ViewModelStore에 SignInViewModel이 등록되지 않은 것이다.

 

 

2.

화면을 회전해서 Activity가 destory 되도록 했다. ViewModelStore에는 by ViewModels에 의해 생성된 SignInViewModel이 등록된 것을 볼 수 있다.

 

 

3.

화면 회전시 Activity는 destroy된 후 재생성된다. 그래서 onCreate가 다시 호출되며, ViewMoelStore를 확인해보면 SignInViewModel이 사라진 것을 볼 수 있다.

 

4.

다시 화면을 회전해서 onDestory를 호출한 후, ViewModelStore에 생성된 SignInViewModel 객체를 확인해봤다.

ViewModelStore에 등록된 ViewModel은 SignInViewModel@26554로, 이전 객체 SignInViewModel@26635와 다른 것을 알 수 있다.

 

더 간단하게는 onDestory 함수에서만 viewModelStore 클리어 함수를 호출하고 onCreate에서 ViewModel 변수가 by에 의해 생성된 이후 ViewModelStore에 있는 ViewModel 주솟값을 찍어보면 달라지는 것을 볼 수 있다.

 

 

 

이것을 통해, ViewModel이 더 이상 필요하지 않은 경우 clear 함수를 호출해 GC에 의해 메모리에서 회수되도록 할 수 있음을 알 수 있다. 물론 현재 사용자에게 보이는 Activity나 Fragment 또는 backStack에 있는 Activity, Fragment 등에서 ViewModel에 대한 참조가 있다면 ViewModelStore에 참조가 없어도 GC에 의해 회수되지는 않을 것이다.

 

 

ViewModelStore의 clear 함수 호출 시점

ComponentActivity의 생성자에서 생명주기가 Lifecycle.Event.ON_DESTROY 로 변경되는 경우, 즉 Activity가 종료되는 경우 ViewModelStore의 clear 함수를 호출하도록 설정하고 있다.

 

Fragment의 ViewModelStore의 clear 함수 호출 시점

Fragment의 ViewModelStore의 clear 함수가 호출되는 시점은 Fragment의 onDestroy lifecycle 함수 호출보다 빠르다.

대략적인 함수 호출 흐름은 아래와 같다.

 

  1. SpecialEffectsController.FragmentStateManagerOperation 클래스의 onComplete 함수
  2. FragmentStateManager의 moveToExpectedState 함수
  3. FragmentStateManager#destroy()
  4. FragmentStore#getNonConfig()
  5. FragmentManagerViewModel#clearNonConfigState()
  6. Fragment의 onDestroy

 

위 흐름에서 1번이 언제 호출되는지는 Fragment 관련 프레임워크 코드를 찾아봐야 해서 더 찾아보지는 않았다.

Fragment 전환 과정에서 moveToExpectedState 함수를 호출하는 함수는 아래 3가지였으며, FragmentStateManager의 destroy 함수까지 호출하는 것은 SpecialEffectsController.FragmentStateManagerOperation의 complete 함수밖에 없었다.

 

  1. FragmentManager의 executeOpsTogether
  2. FragmentStore의 moveToExpectedState
  3. SpecialEffectsController.FragmentStateManagerOperation의 complete 함수

 

Pluu님은 상태기반으로 moveToExpectedState 호출에따라서 동작이 달라져서 중간 과정 설명을 생략했다고 한다.

 

또한 아래와 같이 Fragment에 의해 호출되는 흐름도 있다.

 

  1. Fragment - performDestroy()
  2. FragmentManager - dispatchDestroy()
  3. FragmentManager - clearBackStackStateViewModels()
  4. FragmentManagerViewModel - clearNonConfigState()
  5. FragmentManagerViewModel - clearNonConfigStateInternal()