[Android] 상태 관리 #1 AAC-ViewModel

12 분 소요

앱에서 사용자가 화면영역에서 선택하고 보여졌던 상태들은 안드로이드 lifecycle의 변화에 따라 언제든지 사라질 수 있고, 그에 대한 대응을 해주는 것은 사용자 경험 측면에서 중요하다.

예를 들면, 사용자와 직접 상호작용 하기 위한 Window 를 가지고 있는 Activity 에서는 화면 회전, 언어 변경, 테마 변경과 같은 이유로 Configuration Change(구성 변경) 이 발생하는 경우, Lifecycle 변화에 따라 onDestroy() 콜백을 실행하여 메모리에서 제거됬다가, 다시 생성될 수도 있다. 이런 경우 사용자에게 보여지고 있던 화면의 상태들이 별도의 처리를 해주지 않았다면 사라지기 때문에 사용자 경험상 문제가 될 수 있다.

따라서, 상황에 따라 포그라운드내의 상태를 저장하고 복원해주는 처리가 필요하고 구글에서는 3가지 방법을 가이드하고 있다.

  1. AAC-ViewModel 로의 State Holder : configuration change에도 유지 but, DefaultViewModelProviderFactory 를 사용하지 않은 거나, 사용했을 경우에도 SavedStateHandle 을 이용하여 복원하지 않는 경우, process 종료시 유지못함
  2. SavedState api : process 종료에도 유지 but, Bundle을 사용하여 적고 단순한 형태의 데이터에 적합
  3. Local cache : 모든 경우에 유지, 복잡하고 많은 데이터에 적합 but, api 수행의 위 두가지보다 오랜 지연시간이 존재

AAC-ViewModel 의 기본 매커니즘은 앱 프로세스가 종료될 경우, 메모리 내에서 제거되기 때문에 당연히 상태를 잃게 된다. 하지만 ViewModel 은 Jetpack 에 속하며 Activity 에서 관리되기 때문에, ComponentActivity 가 관리하는 SavedStateRegistry 에 접근할 수 있는 SavedStateHandle 을 제공받을 수 있는 방법이 있다. 기본적으로 ViewModel 생성시에 ComponentActivity#viewModels() 델리게이트를 이용하고 있다면, DefaultViewModelProviderFactory 를 사용하여 SavedStateViewModel 인스턴스를 생성하기 때문에 SavedStateHandle 을 생성자로 전달받을 수 있다.

주의할 점은 ANR 이나 메모리 부족과 같은 이유로 포그라운드 상태로 유지되던 앱 프로세스가 강제로 종료되는 경우에는, 종료되기 전에 2번이나 3번의 방법으로 상태를 저장하지 않았다면 복원할 방법이 없다는 것이다.

만약, 시스템의 홈버튼을 누르거나 다른 앱으로 전환하여 cached process 상태였다면 Lifecycle 의 변화에 따라 onStop() 이후 onSaveInstanceState() 가 호출되면서, View tree 내에 android:id 태그가 작성된 View 였다면 스크롤뷰의 스크롤 위치, EditTextView 의 텍스트정보 등을 기본적으로 저장하고 복원할 수 있으며, onSaveInstanceState() 와 onRestoreInstanceState() 로 화면 내에 추가로 유지할 상태들에 대해 저장하고 복원할 수 있다.

이번 포스팅에서는 해당 내용들 중에 AAC-ViewModel 에 대해 내부 구현을 기반으로 정리해 보겠다.


AAC - ViewModel

먼저 AAC - ViewModel은 MVVM구조에서의 VM과는 역할이 다르다. 이것은 대다수의 개발자들이 아는 상식이고, VM의 경우 MS에서 제시했던 디자인패턴으로 구글에서는 안드로이드에 맞게 설정했으나 개념과 목적자체가 다르다.

AAC-ViewModel의 목적은 configuration change에 따라 activity/fragment의 Lifecycle이 onDestroy ~ onCreate 로 바뀌어 가면서 화면내의 상태를 잃어버리는 문제에 대응하고, 화면과 관련된 UI 로직을 캡슐화 함으로써 상태 홀더(State Holder)를 제공하는 것이다.

Configuration Change

Configuration Change는 Locale변경, 테마변경, 화면 회전 등의 이유로 Configuration 객체의 값이 바뀌는 것을 말하고, 그에따라 Activity가 재생성 되어 화면의 상태를 잃게 된다.

그렇다면 ViewModel은 어째서 구성변경에 영향을 받지 않을까?

ViewModel_Lifecycle

ViewModel의 경우 위의 사진에서 보이듯, Activity의 lifecycle 보다 좀더 큰 범위를 가지고 있어서 구성변경 에도 불구하고 인스턴스가 제거 후 재생성되지 않고, 그렇다고 중복 생성되지도 않는다.

ViewModelProvider & ViewModelStore

ViewModel 인스턴스를 생성하기 위해서 ViewModelProvider라는 ViewModel 생성을 위한 Utility 클래스가 필요하다.

public open class ViewModelProvider
@JvmOverloads
constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) { ''' }

이 클래스의 생성자를 보면 viewModelStore, factory, CreationExtras 가 전달되고 있는데 이러한 여러 인자들을 주입하여 ViewModel 인스턴스를 만들기 쉽게하는 유틸리티 클래스라고 보면 된다. 그리고 이클래스의 get() 메소드로 ViewModel 인스턴스를 생성하여 반환하는데,

 @MainThread
 public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
     val viewModel = store[key]
     if (modelClass.isInstance(viewModel)) {
         (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
         return viewModel as T
     } else {
         @Suppress("ControlFlowWithEmptyBody")
         if (viewModel != null) {
         }
     }
     val extras = MutableCreationExtras(defaultCreationExtras)
     extras[VIEW_MODEL_KEY] = key
				
     return try {
         factory.create(modelClass, extras)
     } catch (e: AbstractMethodError) {
         factory.create(modelClass)
     }.also { store.put(key, it) }
 }

가장 첫줄에서 store라는 변수는 ViewModelStore클래스의 인스턴스이고, 이곳에서 key를 전달하여 viewModel 인스턴스를 가져오고 있다. 먼저 ViewModelStore 클래스를 살펴보자.

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();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    ''' 생략 '''
}

그 내부를 보면 ViewModelStore안에서 HashMap 자료구조로 key-value 형태로 ViewModel 인스턴스를 관리하고 있다. 즉, ViewModel 은 ViewModelStore 내에서 관리되며, Map 자료구조로 특정 ViewModel 인스턴스는 중복해서 생성되지 않는다.

나머지 ViewModelProvider#get() 메소드의 상세한 내용들은 밑에서 다루기로 하고,

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}


public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        ''' {
				
    private ViewModelStore mViewModelStore;
				
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

ViewModelStoreOwner는 ViewModelStore를 반환하는 #getViewModelStore() 메소드를 가지는 인터페이스로 이를 구현하는 클래스는 CompoentActivity와 Fragment 이다.

즉, ComponentActivity와 Fragment가 ViewModelStore 를 관리하고, ViewModelStore 내에서 HashMap으로 ViewModel 인스턴스를 관리하고 있다는 점으로 보아 우리가 만든 ViewModel 인스턴스는 ComponentActivity 나 Fragment 가 각각 하나씩 들고 있으면서 캐싱해놓고 사용한다는 것을 알수있다.

또한, 이 코드로 어떻게 ViewModel 인스턴스가 configuration change에도 유지할 수 있으며, 중복생성 되지 않았는가에 대해 알수있었습니다. 이를 이해하려면 onRetainNonConfigurationInstance() 함수를 살펴봐야 합니다.

public final Object onRetainNonConfigurationInstance() {  
    // Maintain backward compatibility.  
  Object custom = onRetainCustomNonConfigurationInstance();  
  
    ViewModelStore viewModelStore = mViewModelStore;  
    if (viewModelStore == null) {  
        // No one called getViewModelStore(), so see if there was an existing  
 // ViewModelStore from our last NonConfigurationInstance  NonConfigurationInstances nc =  
                (NonConfigurationInstances) getLastNonConfigurationInstance();  
        if (nc != null) {  
            viewModelStore = nc.viewModelStore;  
        }  
    }  
  
    if (viewModelStore == null && custom == null) {  
        return null;  
    }  
  
    NonConfigurationInstances nci = new NonConfigurationInstances();  
    nci.custom = custom;  
    nci.viewModelStore = viewModelStore;  
    return nci;  
}

static final class NonConfigurationInstances {  
    Object custom;  
    ViewModelStore viewModelStore;  
}

ComponentActivity#onRetainNonConfigurationInstance() 함수는 내부적으로 Activity#getLastNonConfigurationInstance() 를 호출하고, 이를 통해 mLastNonConfigurationInstances 의 activity 라는 멤버변수의 값을 가져와서 NonConfigurationInstances 타입으로 캐스팅 합니다.

NonConfigurationInstances 에 바로 ComponentActivity 의 viewModelStore 가 저장됩니다.

@Nullable  
public Object getLastNonConfigurationInstance() {  
    return mLastNonConfigurationInstances != null  
  ? mLastNonConfigurationInstances.activity : null;  
}

static final class NonConfigurationInstances {  
    Object activity;  
    HashMap<String, Object> children;  
    FragmentManagerNonConfig fragments;  
    ArrayMap<String, LoaderManager> loaders;  
    VoiceInteractor voiceInteractor;  
}  
@UnsupportedAppUsage  
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;

이는 Activity#NonConfigurationInstances 타입인데, 여기서 activity 변수가 바로 ComponentActivity 의 NonConfigurationInstances 타입의 인스턴스 입니다. 여기까지 정리하면, ComponentActivity#onRetainNonConfigurationInstance() 가 호출되면 Activity#mLastNonConfigurationInstances 라는 변수에 ComponentActivity 의 ViewModelStore 가 저장된다는 것 입니다. 이를 통해 구성 변경에서도 ViewModelStore 를 잃지 않을 수 있는데요. 그렇다면 onRetainNonConfigurationInstance() 는 언제 호출되는 걸까요?

공식문서에 따르면 해당 함수는 구성 변경이 발생하는 경우 Activity 의 OnStop() 과 OnDestroy() 사이에서 호출됩니다. 이 함수는 궁극적으로 구성 변경에서 ViewModelStore 와 함께 커스텀한 객체가 있는 NonConfigurationInstances 인스턴스를 ActivityThread 에 저장하는 용도로 호출됩니다.

// Activity#attach
final void attach(Context context, ActivityThread aThread,  
        Instrumentation instr, IBinder token, int ident,  
        Application application, Intent intent, ActivityInfo info,  
        CharSequence title, Activity parent, String id,  
        NonConfigurationInstances lastNonConfigurationInstances,  // << 여기를 확인해 주세요.
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,  
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,  
        IBinder shareableActivityToken) {}

// ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {  
    ActivityInfo aInfo = r.activityInfo;
    
    activity.attach(activityBaseContext, this, getInstrumentation(), r.token,  
        r.ident, app, r.intent, r.activityInfo, title, r.parent,  
        r.embeddedID, r.lastNonConfigurationInstances, config,  // << 여기로 전달합니다.
        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,  
        r.assistToken, r.shareableActivityToken, r.initialCallerInfoAccessToken);

그리고 나중에 Activity 가 파괴되고 재 생성될 때 Activity#attach() 인자로 전달되면서 NonConfigurationInstances 가 복원됩니다.

Activity 는 Configuration Change 가 발생하면 Instrumentation 에 의해 파괴 후 재 실행되게 됩니다. 이를 위해 ActivityThread#handleRelaunchActivity() 를 호출하는데요.

public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,  
        @NonNull PendingTransactionActions pendingActions) {
        r.activity.mChangingConfigurations = true;  
}

// ComponentActivity#constructor()
public ComponentActivity() {
  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();  
          }  
      }  
   }  
});

이는 내부적으로 Activity#mChangingConfigurations 를 true 로 설정하는데, 이를 통해 Activity 의 Lifecycle 이 onDestroy() 가 호출되어도 LifecycleObserver 에서 ViewModel#onClear() 를 호출하지 않게 됩니다.

ActivityThread#handleRelaunchActivity() 를 좀 더 살펴보겠습니다.

public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,  
        @NonNull PendingTransactionActions pendingActions) {  

    // First: make sure we have the most recent configuration and most  
    // recent version of the activity, or skip it if some previous call // had taken a more recent version.  synchronized (mResourcesManager) {  
        int N = mRelaunchingActivities.size();  
        IBinder token = tmp.token;  
        tmp = null;  
        for (int i=0; i<N; i++) {  
            ActivityClientRecord r = mRelaunchingActivities.get(i);  
            if (r.token == token) {  
                tmp = r;  
                configChanges |= tmp.pendingConfigChanges;  
                mRelaunchingActivities.remove(i);  
                i--;  
                N--;  
            }  
        }

        handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,  
        pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,  
        "handleRelaunchActivity");

여기서 mRelaunchingActivities 라는 Map 의 ActivityClientRecord 가 액티비티에 대한 정보들을 들고 있어서, 여기서 구성 변경에 의해 재 생성할 액티비티를 찾게 됩니다. 그리고 이 Activity 의 정보인 ActivityClientRecord 를 ActivityThread#handleRelaunchActivityInner 함수로 델리게이트 하게 됩니다.

이렇게 전달된 ActivityClientRecord 를 Activity 재 생성까지 전달하면서 ActivityClientRecord 에 존재하는 NonConfigurationInstances 를 복원할 수 있는 것입니다.

private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,  
        @Nullable List<ResultInfo> pendingResults,  
        @Nullable List<ReferrerIntent> pendingIntents,  
        @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,  
        @NonNull Configuration overrideConfig, @NonNull ActivityWindowInfo activityWindowInfo,  
        @NonNull String reason) {  
    // Preserve last used intent, it may be set from Activity#setIntent().  
  final Intent customIntent = r.activity.mIntent;  
    // Need to ensure state is saved.  
  if (!r.paused) {  
        performPauseActivity(r, false, reason, null /* pendingActions */);  
    }  
    if (!r.stopped) {  
        callActivityOnStop(r, true /* saveState */, reason);  
    }  
  
    handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);  
  
    r.activity = null;  
    r.window = null;  
    r.hideForNow = false;  
    // Merge any pending results and pending intents; don't just replace them  
    if (pendingResults != null) {  
        if (r.pendingResults == null) {  
            r.pendingResults = pendingResults;  
        } else {  
            r.pendingResults.addAll(pendingResults);  
        }  
    }  
    if (pendingIntents != null) {  
        if (r.pendingIntents == null) {  
            r.pendingIntents = pendingIntents;  
        } else {  
            r.pendingIntents.addAll(pendingIntents);  
        }  
    }  
    r.startsNotResumed = startsNotResumed;  
    r.overrideConfig = overrideConfig;  
    r.mActivityWindowInfo.set(activityWindowInfo);  
  
    handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);  
}

ActivityThread#handleRelaunchActivityInner() 는 Activity 를 차례로 onPause() - onStop() - onDestroy() 까지 호출시켜 파괴시킨 후, ActivityThread#handleLaunchActivity() 를 호출시킴으로써 Activity 를 재 생성 합니다. 그리고 결국ActivityThread#handleLaunchActivity() 은 내부적으로 위에서 보았던 ActivityThread#performLaunchActivity() 를 호출하게 되는 것입니다.

정리하자면,

  1. ViewModelStoreOwner 의 역할을하는 ComponentActivity가 ViewModelStore 인스턴스를 생성하고 반환하는 getViewModelStore()를 구현한다.
  2. ViewModel 인스턴스를 만들기 위해 필요한 Utility 클래스인 ViewModelProvider에 생성자로 getViewModelStore()의 viewModelStore 인스턴스를 전달한다.
  3. ViewModelStore는 HashMap 구조로 ViewModel 인스턴스들을 Key-Value 형태로 캐싱하고 있다.
  4. ViewModelStore는 configuration change 에 따라 activity가 재생성되어도 ActivityThread 에서 Activity 의 정보인 ActivityClientRecord 를 추출하여 생성 과정의 델리게이트로 전달한다.
  5. 최종적으로 새로 생성된 Activity#attach() 에 ActivityClientRecord 의 NonConfigurationInstances 가 전달되면서, viewModelStore 인스턴스를 복원할 수 있게 되고 이를 통해 Activity 가 파괴되고 제거되지 않고 유지시킬 수 있다.

자, 이제 왜 구성변경에 영향을 받지 않는지 확인했으니 ViewModelProvider로 돌아가서 ViewModel 인스턴스를 어떻게 생성하고 제거되고 있는지 살펴보겠다.

ViewModelProvider

public open class ViewModelProvider
@JvmOverloads
constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) { 

    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
		
    public constructor(
	owner: ViewModelStoreOwner, 
	factory: Factory
    ) : this(owner.viewModelStore, factory, defaultCreationExtras(owner))

}

internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras {
    return if (owner is HasDefaultViewModelProviderFactory) {
        owner.defaultViewModelCreationExtras
    } else CreationExtras.Empty
}

internal fun defaultFactory(owner: ViewModelStoreOwner): Factory = 
    if (owner is HasDefaultViewModelProviderFactory) 
        owner.defaultViewModelProviderFactory 
    else 
        instance

ViewModel 생성에는 ViewModelStore, ViewModelProvider.Factory, CreationExtras 세가지의 인스턴스가 필요하다.

public abstract class CreationExtras internal constructor() {
    internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()

    public interface Key<T>

    public abstract operator fun <T> get(key: Key<T>): T?

    object Empty : CreationExtras() {
        override fun <T> get(key: Key<T>): T? = null
    }
}

CreationExtras 는 ViewModel 생성에 필요한 application, savedStateRegistryOwner, ViewModelStoreOwner, 그리고 CompoentActivity 가 가지는 Intent의 extras나 Fragment가 transaction으로 전환될 때 전달되는 Bundle Argument 들을 Map 자료구조에 저장해서 가져오는 용도로 사용한다.

팩토리패턴으로 객체 생성을 분리시키고 보니, ViewModel 인스턴스 생성에 필요한 요소들을 파라미터로 넘기기에는 Factory 인스턴스 생성 당시에 그정보들을 모두 가지기 어렵기 때문에 사용한다.

ViewModelProvider.Factory 는 인터페이스로 우리가 흔히 알고있는 디자인패턴의 생성패턴중 팩토리패턴이 사용되었다.

public interface Factory {
		
    public fun <T : ViewModel> create(modelClass: Class<T>): T {
        throw UnsupportedOperationException(
            "Factory.create(String) is unsupported.  This Factory requires " +
                "`CreationExtras` to be passed into `create` method."
        )
    }

    public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
        create(modelClass)

    companion object {
        @JvmStatic
        fun from(vararg initializers: ViewModelInitializer<*>): Factory =
            InitializerViewModelFactory(*initializers)
    }
				
}

factory 인터페이스로 인스턴스의 생성 로직을 분리하고, 이에 대한 구현체들은 총 3가지이다.

  • NewInstanceFactory
  • AndroidViewModelFactory
  • SavedStateViewModelFactory

ViewModel 클래스가 아닌 application을 가지는 AndroidViewModel 클래스를 상속받는다면 AndroidViewModelFactory로 생성되며, AndroidViewModelFactory에서 application이 null 일때 NewInstanceFactory를 통해 생성한다. default로는 SavedStateViewModelFactory로 생성된다.

ViewModelProvider 생성자 세가지 모두 ViewModelStoreOwner 로 부터 가져오는데 위의 내용에서 알았듯이, ComponentActivity가 가지고 있는 ViewModelStore 나 default 값들을 가져온다.

@NonNull
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    if (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
                getApplication(),
                this,
                getIntent() != null ? getIntent().getExtras() : null);
    }
    return mDefaultFactory;
}

@NonNull
@Override
@CallSuper
public CreationExtras getDefaultViewModelCreationExtras() {
    MutableCreationExtras extras = new MutableCreationExtras();
    if (getApplication() != null) {
        extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, getApplication());
    }
    extras.set(SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY, this);
    extras.set(SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY, this);
    if (getIntent() != null && getIntent().getExtras() != null) {
        extras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, getIntent().getExtras());
    }
    return extras;
}
		
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
   	noinline extrasProducer: (() -> CreationExtras)? = null,
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(
        VM::class,
        { viewModelStore },
        factoryPromise,
        { extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
    )
}

getDefaultViewModelProviderFactory() 에서는 SavedStateViewModelFactory 인스턴스를 application, savedStateRegistryOwner, intent.extras 로 생성하여 가져오는 모습이고,

getDefaultViewModelCreationExtras() 에서는 새로운 CreationExtras 인스턴스에 application, SavedStateRegistryOwner, ViewModelStoreOwner, Intent.extras 를 넣어 가져오는 모습이다.

여기서 알수있는 사실은 default값들은 모두 savedStateRegistry 에 관여되어 있고 우리가 일반적으로 ViewModel 인스턴스를 by viewModels() 로 생성할 때 상태관리의 2번방법을 사용하고 있다는 점들이다.

다시 돌아가서, ViewModelProvider 인스턴스로 생성자 타임에 3가지 파라미터가 필요했고 ViewModelProvider#get() 메소드를 통해 ViewModel 인스턴스 생성을 보면

 @MainThread
 public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
     val viewModel = store[key]
     if (modelClass.isInstance(viewModel)) {
         (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
         return viewModel as T
     } else {
         @Suppress("ControlFlowWithEmptyBody")
         if (viewModel != null) {
         }
     }
     val extras = MutableCreationExtras(defaultCreationExtras)
     extras[VIEW_MODEL_KEY] = key
				
     return try {
         factory.create(modelClass, extras)
     } catch (e: AbstractMethodError) {
         factory.create(modelClass)
     }.also { store.put(key, it) }
 }

get() 메소드 내에서 ViewModelStore 내에 인스턴스가 존재하는지 확인하고 존재하지 않는다면 factory.create() 를 수행해서 ViewModel 인스턴스를 생성하고 ViewModelStore에 저장한뒤 반환하고 있고,

이미 존재한다면 해당 Factory 구현체에 onRequery() 에 ViewModel 인스턴스를 넣어 수행 시킨 뒤 반환한다.

onRequery 메소드의 경우 ViewModelProvider.Factory의 구현체 3가지중에 SavedStateViewModelFactory 만이 이것을 구현하고 있으므로 as? 로 캐스팅했을 때 null이 아니면 onRequry()를 수행시키는 것이다.

해당부분은 다음 챕터에서 설명하며, 그렇다면 ViewModel 인스턴스가 생성되고 나서 제거되는 과정은 어떨까?

ViewModelStore.clear()

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();

    if (lifecycle == null) {
        throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                + "constructor. Please make sure you are lazily constructing your Lifecycle "
                + "in the first call to getLifecycle() rather than relying on field "
                + "initialization.");
    }
				
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                mContextAwareHelper.clearAvailableContext();
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
                mReportFullyDrawnExecutor.activityDestroyed();
            }
        }
    });
}

ComponentActivity 는 lifecycleOwner 의 구현체이고, 생성자에서 해당 lifecycle 에 옵저버를 등록하여, ON_DESTROY 이벤트가 수행되는 시점에 getViewModelStore() 로 인스턴스를 가져와 clear() 한다.

이때 Activity#isChangingConfigurations() 를 통해 현재 configuration change가 일어났는지 확인하고, 그렇지 않은 경우에 대해서만 viewmodelstore.clear() 를 호출합니다. ActivityThread#handleRelaunchActivity() 에서 ActivityThread#mChangingConfigurations 변수값을 true 로 만들어 두었던 점을 기억하세요.

이 때문에 Activity 는 구성 변경에서 제거 후 재 생성 당하는 과정에서, Activity 에 등록되어 Lifecycle 을 인지하고 있는 ViewModel 들은 옵저버에 의해 clear 되지 않습니다.

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();
		
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

구성 변경이 아닌 경우, ON_DESTROY 이벤트가 수신됬을 때 ViewModelStore 로 관리되는 ViewModel 들을 일괄적으로 모두 clear 시킵니다.

정리해보자면,

  1. ViewModel 인스턴스를 생성하기 위해서는 ViewModelProvider 클래스가 필요하다.
  2. ViewModelProvider 인스턴스 생성에는 ViewModelStore, ViewModelProvider.Factory, CreationExtras 가 필요하다.
  3. ViewModelStore는 viewModel 인스턴스를 Map 자료구조로 관리
  4. CreationExtras는 ViewModelFactory 인스턴스 생성시점에 필요한 application, ViewModelStore(Owner), intent.extras를 모두 갖기 어렵기 때문에 주입의 편리를 위해 사용
  5. ViewModelProvider.Factory는 팩토리패턴으로 생성 로직을 분리하고, 구현체는 NewInstanceFactory, AndroidViewModelFactory, SavedStateViewModelFactory가 존재
  6. by viewModels() 델리게이트 함수는 DefaultViewModelProviderFactory 로 SavedStateViewModelProviderFactory 를 이용하기 때문에 savedStateViewModel 인스턴스가 생성됩니다.
  7. activity의 lifecycle 이 ON_DESTROYED 이벤트가 수행될 때, 구성 변경이 아닌 경우 ViewModelStore.clear() 로 ViewModel 인스턴스들을 일괄적으로 clear 합니다.

다음 챕터에서는 SavedState api로 상태관리 2번과정이 어떻게 구현되고 있고 어떤목적을 달성하고 있는지 정리해보겠습니다.

태그:

카테고리:

업데이트:

댓글남기기