Android

AAC ViewModel은 어떻게 onDestroy에서 살아남을 수 있었을까

seokzoo 2022. 9. 14. 00:00
반응형

1. AAC ViewModel이란?

  • AAC ViewModel(이하 ViewModel)은 ViewModelStoreOwner의 수명주기를 인식하며, 화면 회전같은 configuration change에서 데이터가 생존할 수 있도록 해준다.

  • 또한 목적에 따라 ViewModel, onSaveInstanceState, Persistent Storage를 적절히 이용해 데이터를 저장할 수 있도록 한다. 이는 저장소 위치, 시스템에서 프로세스 중단시에도 데이터 유지, 사용자가 액티비티를 완전히 닫거나 finish()등 에서도 데이터가 유지되는지에 따른 차이가 있으니 필요에 맞게 단독 또는 조합하여 사용해야 한다.

  • ViewModel은 onSaveInstanceState나 Persistent Storage와는 다르게 메모리에 데이터를 유지하기 때문에 데이터를 Read/Write 속도가 빠르다.

ViewModel은 위의 사진처럼 화면이 회전하거나, onDestroy가 호출되며 onCleared를 호출하기 전까지 살아있을 수 있다.

액티비티의 경우 ComponentActivity.java 에서 getLifecycle().addObserver을 통해 Lifecycle의 상태가 ON_DESTROY이며, Configuration이 변경(화면 회전과 같은)이 일어나지 않았을 경우 ViewModelStore에 접근해 clear()함수를 실행하고, ViewModelStore안에 있는 ViewModel의 해시값(정보)를 지우게 된다.
이와 같은 동작으로 인해 화면 변경에도 데이터가 살아남을 수 있다.(isChangingConfigurations()가 거짓이라면 getViewModelStore.clear()가 호출되지 않기때문이다.)

   getLifecycle().addObserver(new LifecycleEventObserver() {
              @Override
              public void onStateChanged(@NonNull LifecycleOwner source,
                      @NonNull Lifecycle.Event event) {
                  if (event == Lifecycle.Event.ON_DESTROY) {
                      // Clear out the available context
                      mContextAwareHelper.clearAvailableContext();
                      // And clear the ViewModelStore
                      if (!isChangingConfigurations()) {
                          getViewModelStore().clear();
                      }
                  }
              }
          });

    //ViewModelStore.java
    public final void clear() {
          for (ViewModel vm : mMap.values()) {
              vm.clear();
          }
          mMap.clear();
      }

ViewModel의 좀 더 정확한 내부의 구조에 따른 실행은 아래 블로그를 정독해보자.(생성부터 파괴까지에 대해 잘 설명이 되어있다!)
https://pluu.github.io/blog/android/2020/05/04/viewmodel-b-to-d/

2. ViewModel 사용법

dependencies { 
  implementation 'androidx.activity:activity-ktx:1.2.2' 
  implementation 'androidx.fragment:fragment-ktx:1.3.3' 
.. }

먼저 모듈 수준의 gradle에 두 항목을 implement 해준다.

 class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

ViewModel()을 위의 예제와 같이 상속해주고,

  class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

ViewModel 변수를 선언할 때 by ViewModels()를 붙여주면 액티비티가 다시 만들어져도 같은 인스턴스를 가진 MyViewModel을 호출할 수 있다. 액티비티가 완전히 종료된다면 ViewModel의 리소스는 onCleared()에서 지워진다.

ViewModelStore은 이름 그대로 ViewModel을 저장하는 클래스인데, 이곳에서 clear를 호출해주기도 한다. 생명주기가 종료될 때 clear()함수를 호출하여 ViewModel이 종료되게끔 동작한다.

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared(); // onCleared()를 호출한다.
        }
    }

    ...
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear(); // ViewModel의 clear()함수 내부에 onCleared()를 호출한다.
        }
        mMap.clear();
    }
}

3. activityViewModels는 어떨 때 사용할까?

먼저 액티비티에서는 activityViewModels를 호출할 수 없다. 오직 프래그먼트에서만 사용할 수 있다. 프래그먼트가 생성된 액티비티의 라이프 사이클에 ViewModel을 종속시키고 싶을 때 사용한다.

액티비티에 여러개의 프래그먼트를 가지고 같은 데이터를 공유하고 싶다면, 또는 프래그먼트에서 처리한 데이터를 activity에서 데이터 갱신을 하고싶은 경우 이용해주면 된다.

  • 액티비티에 여러개의 프래그먼트가 존재하는 경우, 프래그먼트는 각각의 생명주기에 맞는 ViewModel을 가질 수 있는데, 이는 서로 다른 인스턴스를 가지게 된다.
  • 하지만 여러개의 프래그먼트가 같은 인스턴스를 통해 같은 데이터를 이용하고 싶은 경우, SharedViewModel을 만들어 같은 인스턴스를 가지고 데이터를 공유할 수 있는데 이는 activityViewModels을 통해 가능하다.
  • 이렇게 생성된 ViewModel을 액티비티에서는 viewModels()를 통해 이용하며, 같은 인스턴스를 가리킨다.
  val mainViewModel : MainViewModel() by activityViewModels()

그렇다면 어떻게 같은 액티비티의 데이터를 공유할 수 있을까?

@MainThread
public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
    VM::class, { requireActivity().viewModelStore },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }
)

activitiyViewModels의 구현을 보면 requireActivity를 통해 프래그먼트의 부모 액티비티를 얻게된다. 그 얻게된 부모 액티비티를 통해 Activity.viewModelStore에서 모델의 해시값을 동일하게 유지하기 때문이다.
즉, ViewModel의 인스턴스가 둘 다 같기 때문에 같은 데이터를 유지할 수 있게 된다.

4. 출처

https://developer.android.com/topic/libraries/architecture/viewmodel
https://pluu.github.io/blog/android/2020/05/04/viewmodel-b-to-d/
https://kotlinworld.com/88?category=918952

반응형