ViewModel이 구성변경에도 인스턴스를 유지하는 이유

Kenneths
Kenneth Android
Published in
7 min readJan 8, 2023
Photo by Ferenc Almasi on Unsplash

안드로이드에서 구성변경(Configuration Change)이 발생할 때 ViewModel 객체는 Activity가 재생성 되더라도 파괴되지 않고 객체가 유지 되는것을 볼 수 있습니다.

여기서의 ViewModel은 MicroSoft에서 제시한 MVVM의 ViewModel 아닌 Android Architecture Component의 ViewModel 입니다.

Activity의 마지막 생명주기인 onDestroy()은 파괴되고 사라지기전에 호출되는 생명주기 마지막 오버라이드 함수인 이후 호출되는 ViewModel의 생명주기 중 하나인 onCleared()에서 여러가지 해제하는 작업들을 합니다.

하지만 여기서 하나 의문점은 Activity의 종료가 구성변경과 같은 재생성에 의한 종료인지 finish()를 통한 종료인지 ViewModel은 어떻게 알 수 있을까요?

만약 단순 구성변경으로 발생한 Activity의 종료일 때 ViewModel의 onCleared()에서 여러 작업들을 해제한다면 사용자들은 잘못된 정보를 전달 받을 확률이 높습니다. 이 때문에 Activity가 재생성으로 인한 파괴(Destroy)라는 것을 파악하고 전파해야 합니다. 어떻게 이런 동작들이 이루어지는지 ViewModel과 Activity의 내부 코드들을 차근차근 살펴보려고 합니다.

아래 그림은 Activity와 ViewModel의 라이프사이클을 순서를 나타내는 이미지입니다.

일단, 가장 먼저 ViewModel에서 onCleared()가 호출되는 함수를 따라가보겠습니다.

ViewModel에서 onCleared()는 final 함수인 clear()에서 호출이됩니다. 이 clear()는 누가 호출하는지 따라가보면 ViewModelStore라는 객체에서 호출하는 것을 볼 수 있습니다.

ViewModelStore는 ViewModel들을 Map 형태로 저장하기는 코드로 구성 되어있습니다. ViewModelStore의 클래스 내부를 살펴보면 String Key값과 ViewModel Value를 가지는 해시맵을 가지고 있습니다.

이 클래스는 단순히 해시맵에 putget하기 위한 함수들을 제공합니다. 해시맵에 들어가는 키 값은 기본값으로는 androidx.lifecycle.ViewModelProvider.DefaultKey:modelClass.canonicalName로 생성합니다.

clear()를 보면 해시맵의 Value들을 재귀하여 ViewModel의 clear()를 호출하고 해시맵을 clear() 하는 것을 볼 수 있습니다. 결국 이 ViewModelStore 가 ViewModel의 clear()를 호출하는 것을 알아냈고 이 함수는 ComponentActivity에서 호출되는 것을 확인할 수 있습니다.

ComponentActivity에서는 LifecycleEventObserver를 라이프사이클에 관찰자로 등록하고 onDestroy!isChangingConfigurations()가 두개 모두 만족할 경우 ViewModelStoreclear()가 호출되어 ViewModel까지 전달되는 것을 볼 수 있습니다.

isChangingConfigurations()Activity 클래스의 내부 멤버변수인 mChangingConfigurations를 리턴해주는 함수입니다. 이제 이 mChangingConfigurationstrue 혹은 false로 변경하는 곳이 어디인지를 찾으면 됩니다.

해답은 ActivityThread에 있었습니다. 이 클래스는 액티비티 매니저의 요청들을 어플리케이션 프로세스에서 메인스레드로 실행할 수 있게 Activity나 Broadcast 그리고 다른 작업들을 스케쥴링하고 실행하는 것을 관리해주는 클래스입니다.

이 클래스에 오버라이드된 activityLocalRelaunch()를 살펴보면 ActivityClientRecord에 기록된 activity에 mChangingConfigurationtrue로 설정해준 것을 확인할 수 있습니다.

여기까지 mChangingConfigurations의 변수를 통해 getViewModelStore().clear()가 실행여부를 밝혀냈습니다. 하지만 여기서 ViewModel의 생성도 결국 Activity에서 이루어지는데 Activity가 완전히 파괴되고 다시 만들어질 때 ViewModel의 인스턴스를 어떻게 유지할지에 대한 궁금증이 생깁니다. 여기에는 ViewModelStoreOwner에 해답이 있습니다.

ViewModelStoreOwner 인터페이스 주석 설명을 보면 이 인터페이스 구현체의 책임은 구성 변경중에도 ViewModelStore를 유지하고 이 범위가 파괴될 때 ViewModelStore.clear()를 호출하는 것이라고 적혀있습니다.

즉, ViewModelStoreOwner는 각 Owner(일반적으로 Activity, Fragment가 해당 됩니다.)들이 구성변경이 일어나서 파괴되고 재생성되어도 인스턴스를 유지하는 역할을 합니다. 만약 Activity나 Fragment같은 Owner들은 완전히 파괴되고 재생성될 여지가 없다면 에서 ViewModelStore객체에 clear()를 호출하여 더이상 ViewModel이 사용되지 않는다는 사실을 전파해야합니다.

ViewModelStoreOwner의 구현체인 ComponentActivity의 코드를 확인해보겠습니다.

코드를 살펴보면 getLastNonConfigurationInstane()를 호출하여 NoConfigurationInstance라는 클래스를 캐스팅해서 가져온 후 ComponentActivity의 ViewModelStore 멤버변수로서 할당해줍니다. Activity가 구성변경에 의해 재생성될 때 ComponentActivity에 있는 NonConfigurationInstance클래스는 이전 ViewModelState의 인스턴스를 포함하고 있습니다. Activity가 처음 생성될 때 NonConfigurationInstance는 null을 반환하고 그런 경우에는 ViewModelStore를 새로 생성합니다.

조금더 내부 코드를 살펴보겠습니다.

Activity가 구성변경이 발생하여 재생성될 때 ComponentActivity 의 onRetainNonConfigurationInstance 함수가 호출되고 NonConfigurationInstance(ComponentActivity.java)의 객체를getLastNonConfigurationInstance()를 호출하여 Activity의 mLastNonConfigurationInstances.activity를 반환받는데 이 객체는 안드로이드 시스템에서 재생성된 후의 액티비티를 전달한 것입니다.

Activity가 재생성후에 Activity.java클래스에서 mLastNonConfigurationInstances 객체는 attach()에서 lastNonConfigurationInstances를 전달받아 mLastNonConfigurationInstances에 할당합니다.

마무리

지금까지 ViewModel이 구성변경에도 인스턴스를 유지하는 방법과 onCleared()의 호출 조건을 알아보았습니다.

ViewModel의 인스턴스 유지 과정을 살펴보니 Activity에 대한 코드의 이해도도 증가되고 ViewModel의 구성방식들도 이해하게되어 좋았습니다. 안드로이드 개발자라면 ViewModel을 잘 활용하여 구성변경에도 데이터를 유지하여 사용자 경험을 증진시키는게 하나의 임무라고 생각합니다 😄 긴글 봐주셔서 감사합니다.

--

--

Kenneths
Kenneth Android

사용자들에게 편리하고 AweSome UI, UX를 경험해주고 싶은 상위 티어 개발자가 되고싶어 달려가고있는 개발자입니다. 다양한 내용들을 공유하려고 합니다.