ViewModel in Activity Under the Hood: How It Survives Recreation
What actually happens to ViewModel when Activity is destroyed and recreated? We break down the entire chain: ViewModelStore, NonConfigurationInstances, ActivityThread, and ActivityClientRecord. We show in detail where and how state is stored, and how Android magically restores everything during configuration changes.
Introduction
This article doesn’t cover working with ViewModel, assuming this topic is already familiar. The main focus is on how ViewModel survives configuration changes. But first — a brief introduction to ViewModel.
ViewModel is a component of the MVVM architectural pattern that was provided by Google as a primitive allowing it to survive configuration changes. Configuration changes, in turn, are states that force activity/fragment to be recreated, which is exactly the state that ViewModel can survive. Popular configurations that lead to Activity recreation:
- Screen orientation changes (screenOrientation): portrait/landscape
- Screen direction changes (layoutDirection): rtl/ltr
- Application language changes (locale)
- Font size/screen ratio changes
Of course, there’s a way to tell the system that Activity doesn’t need to be recreated when configuration changes occur. The android:configChanges
flag is used in AndroidManifest.xml
in the <activity/>
tag to specify which configuration changes the system should not recreate Activity for, but instead pass control to the Activity.onConfigurationChanged()
method.
<activity
android:name="MainActivity"
android:configChanges="layoutDirection|touchscreen|density|orientation|keyboard|locale|keyboardHidden|navigation|screenLayout|mcc|mnc|fontScale|uiMode|screenSize|smallestScreenSize"
/>
However, that’s not what we’re talking about now. Our goal is to understand how ViewModel
manages to survive all configuration changes and preserve its state.
ViewModel Declaration
With the advent of delegates in Kotlin, developers gained the ability to significantly simplify the creation and use of components. Now declaring ViewModel
using delegates looks like this:
class MainActivity : ComponentActivity() {
private val viewModel by viewModel<MyViewModel>()
}
Without delegates, creating a ViewModel object using explicit ViewModelProvider call looks like this:
class MainActivity : ComponentActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// In older versions ViewModelProvider was part of lifecycle-viewmodel
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// After adapting ViewModel for KMP and moving ViewModelProvider to lifecycle-viewmodel-android
// you can and should use the overloaded factory method create:
viewModel = ViewModelProvider.create(owner = this).get(MyViewModel::class)
// Alternative way to create ViewModel (equivalent to the previous one)
viewModel = ViewModelProvider.create(store = this.viewModelStore).get(MyViewModel::class)
}
}
The ViewModelProvider.create
method has parameters with default values, so at the bytecode level the compiler will create several overloaded versions of the method (overloads). This allows calling it with different numbers of arguments: only with store
, with store
and factory
, or with all parameters including extras
.
Jetpack ViewModel now supports Kotlin Multiplatform (KMP), which allows using it not only on Android, but also on iOS, Desktop, and Web. This became possible thanks to separation into two modules:
lifecycle-viewmodel(expected): KMP module without Android binding. lifecycle-viewmodel-android(actual): module for working with ViewModelStoreOwner and ViewModelProvider on Android.
Starting from version 2.8.0-alpha03, lifecycle-* artifacts now officially support Kotlin Multiplatform! This means that classes like ViewModel, ViewModelStore, ViewModelStoreOwner, and ViewModelProvider can now be used in common code.
Later in the article we’ll examine exactly the viewmodel:2.8.0+ version. If the sources in the version you’re currently on differ slightly, don’t worry - with the addition of KMP support, the internal structure changed a bit, but the implementation and internal logic are the same as before KMP support.
ViewModelStoreOwner?
As we can see above, we don’t manually create a ViewModel object, but only pass its class type to ViewModelProvider, which handles creating the instance itself.
Note that we also pass the parameter owner = this
to the ViewModelProvider.create
method. If we look at the create method sources, we can notice that it requires owner type: ViewModelStoreOwner:
public actual companion object {
@JvmStatic
@Suppress("MissingJvmstatic")
public actual fun create(
owner: ViewModelStoreOwner, // <- we're interested in this type
factory: Factory,
extras: CreationExtras,
): ViewModelProvider = ViewModelProvider(owner.viewModelStore, factory, extras)
}
If you’re wondering why the create()
method can be called without passing values for the factory
and extras
parameters (even though they’re mandatory):
ViewModelProvider.create(owner = this)
This is because the code uses KMP (Kotlin Multiplatform). In the expect-declaration for create(), default values are already set for factory and extras, so passing them explicitly is optional.
public expect class ViewModelProvider {
....
public companion object {
public fun create(
owner: ViewModelStoreOwner,
factory: Factory = ViewModelProviders.getDefaultFactory(owner),
extras: CreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
): ViewModelProvider
}
....
}
You can see more details in the sources: ViewModelProvider.kt
Diving into ViewModelStore / Owner
So when calling the ViewModelProvider.create()
method for the owner
parameter, we pass this
(the activity itself), and as you can guess, this means that the activity implements (inherits from) the ViewModelStoreOwner
interface. Let’s look at the sources of this interface: ViewModelStoreOwner:
public interface [[[ViewModelStoreOwner |https://github.com/androidx/androidx/blob/androidx-main/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProvider.kt]]] {
/**
* The owned [ViewModelStore]
*/
public val viewModelStore: ViewModelStore
}
ViewModelStoreOwner
is an interface with a single field that represents a ViewModelStore
(view models holder). Components like ComponentActivity, Fragment, NavBackStackEntry inherit from ViewModelStoreOwner
.
The official documentation states:
A scope that owns ViewModelStore. A responsibility of an implementation of this interface is to retain owned ViewModelStore during the configuration changes and call ViewModelStore.clear, when this scope is going to be destroyed.
ViewModelStoreOwner Responsibilities:
- Store ViewModelStore during configuration changes.
- Clear ViewModelStore when ComponentActivity/Fragment is destroyed — in the
onDestroy()
state. All ViewModels that ViewModelStore holds are deleted.
We’ve determined that ViewModelStoreOwner is just an interface containing no logic of its own. It’s implemented by components like:
ComponentActivity
(and its descendants:FragmentActivity, AppCompatActivity
)Fragment
(and its derivatives:DialogFragment
,BottomSheetDialogFragment
,AppCompatDialogFragment
).NavBackStackEntry
- Class from the Jetpack Navigation library (aka androidx navigation)
Next, we’re interested in ViewModelStore itself:
ViewModelStore is a class that internally delegates management to a Map (LinkedHashMap) collection for storing ViewModel by key:
private val map = mutableMapOf<String, ViewModel>()
By default, the full class name (including its package) is used as the key. This key is generated as follows in the sources of the ViewModelProviders utility class (not to be confused with ViewModelProvider):
private const val VIEW_MODEL_PROVIDER_DEFAULT_KEY: String = "androidx.lifecycle.ViewModelProvider.DefaultKey"
internal fun <T : ViewModel> getDefaultKey(modelClass: KClass<T>): String {
return "$VIEW_MODEL_PROVIDER_DEFAULT_KEY:$modelClass.canonicalName"
}
Thus, for MyViewModel the key would look like: androidx.lifecycle.ViewModelProvider.DefaultKey:com.example.MyViewModel
.
Since ViewModelStore is based on Map
, it delegates all basic operations like put, get, keys, and clear
to the internal Map (LinkedHashMap).
Accordingly, since the internal implementation of ViewModelStore
relies on Map, it also delegates its put
, get
, key
, clear
methods to the internal Map
(LinkedHashMap). The clear()
method deserves special attention:
public open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
...
/**
* Clears internal storage and notifies `ViewModel`s that they are no longer used.
*/
public fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}
Let’s understand what’s happening here. When our ViewModelStoreOwner
(in the form of ComponentActivity
or Fragment
) finally dies (death not related to recreation due to configuration changes), it calls the clear()
method on ViewModelStore
.
In the clear()
method, the for
loop goes through all values (view models
) stored inside the internal HashMap
and calls the internal clear()
method on each ViewModel
. This method, in turn, initiates the call to the onCleared()
method on our ViewModel
.
onCleared()
is a method that we can override in our ViewModel
, and it’s called only when the ViewModel
is finally destroyed, when the activity or fragment also finally terminates.
public actual abstract class ViewModel {
...
protected actual open fun onCleared() {} // <- onCleared method that can be overridden
@MainThread
internal actual fun clear() {
impl?.clear()
onCleared() // <- call to onCleared method
}
}
Thus, the clear()
method ensures that all resources and background tasks associated with ViewModel
are properly freed before destruction. Accordingly, the viewModelStore.clear()
method itself is called by ViewModelStoreOwner
(in the form of ComponentActivity
or Fragment
). Let’s choose ComponentActivity
as an example to understand how cleanup works.
Below is a code fragment from ComponentActivity
that tracks its destruction and calls viewModelStore.clear()
:
@Suppress("LeakingThis")
lifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) { // <- ON_DESTROY state is the trigger
// Clear out the available context
contextAwareHelper.clearAvailableContext()
// And clear the ViewModelStore
if (!isChangingConfigurations) { // <- check if ViewModelStore can be cleared
viewModelStore.clear() // <- ViewModelStore cleanup
}
reportFullyDrawnExecutor.activityDestroyed()
}
}
)
In this code, an observer is added to the activity lifecycle using LifecycleEventObserver
. When the activity reaches the ON_DESTROY
state, a check is made to see if a configuration change is occurring (isChangingConfigurations
). If the activity is truly dying permanently (and not being recreated), the viewModelStore.clear()
method is called, which clears all ViewModels associated with the activity.
We can see that checking the ON_DESTROY
state combined with the condition if (!isChangingConfigurations) ensures that the destruction is not due to a configuration change. Only in this case is the ViewModelStore cleared and all ViewModel instances associated with this activity are removed.
In this article we examine the internal methods of the
ComponentActivity
class in detail, starting from version androidx.activity:activity:1.9.0-alpha01, when it was rewritten in Kotlin.If you have an older version of the library installed and see Java implementation — don’t worry. The logic and main methods remained the same, so all presented concepts and explanations will be relevant.
ComponentActivity.onDestroy()
→getViewModelStore().clear()
→MyViewModel.onCleared()
Now we’ve understood the cleanup and destruction process of ViewModel
. Let’s move to the next stage — let’s examine in detail how ViewModel
object creation happens when we pass it to ViewModelProvider
:
ViewModelProvider.create(owner = this).get(MyViewModel::class)
Yes, we can clarify that ViewModelProvider.create
is a function with default values. For example:
ViewModelProvider.create(owner = this).get(MyViewModel::class)
Earlier we examined one of the overloaded ViewModelProvider.create
methods (function with default arguments). This is a factory method that takes at minimum ViewModelStore
or ViewModelStoreOwner
, creates a ViewModelProvider
object, and completes its work.
Now we’re interested in the next key method — get
, which takes the ViewModel
class as a parameter. ViewModelProvider
delegates its work to the ViewModelProviderImpl
class:
public actual open class ViewModelProvider private constructor(
private val impl: ViewModelProviderImpl,
) {
...
@MainThread
public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
impl.getViewModel(modelClass) // <- call to getViewModel method belonging to ViewModelProviderImpl
}
Google developers extracted the common ViewModel creation logic into a separate ViewModelProviderImpl object. This allowed avoiding code duplication on different platforms in KMP. The reason is that expect-classes in Kotlin Multiplatform cannot contain default method implementations. If they could, the implementation would be directly inside the expect-version of ViewModelProvider, without needing to extract it to a separate object. However, due to this limitation, ViewModelProviderImpl was created, which contains the common ViewModel creation logic for all platforms.
Original comment:
Kotlin Multiplatform does not support expect class with default implementation yet, so we extracted the common logic used by all platforms to this internal class.
Sources of the getViewModel()
method in ViewModelProviderImpl.kt
:
internal fun <T : ViewModel> getViewModel(
modelClass: KClass<T>,
key: String = ViewModelProviders.getDefaultKey(modelClass),
): T {
val viewModel = store[key] // 1. Get viewmodel from ViewModelStore if it exists
if (modelClass.isInstance(viewModel)) {
if (factory is ViewModelProvider.OnRequeryFactory) {
factory.onRequery(viewModel!!)
}
return viewModel as T
}
val extras = MutableCreationExtras(extras)
extras[ViewModelProviders.ViewModelKey] = key
// 2. Create viewmodel and put it in ViewModelStore
return createViewModel(factory, modelClass, extras).also { vm -> store.put(key, vm) }
}
When calling ViewModelProvider.create()
under the hood, the getViewModel()
method is called, which performs the following steps:
- Checks for the existence of a
ViewModel
object inViewModelStore
by the given key. If the object already exists, it’s returned. - If the object is not found, a new
ViewModel
instance is created, which is then put inViewModelStore
for subsequent use.
Where is ViewModelStore saved?
Now that we know the complete process of creating ViewModel
and placing it in ViewModelStore
, a logical question arises: if all ViewModel
s are stored inside ViewModelStore
, and ViewModelStore
itself is in ComponentActivity
or Fragment
, which implement the ViewModelStoreOwner
interface, then where and how is the ViewModelStore
object itself stored?
To find the answer to the question about ViewModelStore
storage, let’s look at how ComponentActivity
implements the ViewModelStoreOwner
interface:
override val viewModelStore: ViewModelStore
get() {
checkNotNull(application) {
("Your activity is not yet attached to the " +
"Application instance. You can't request ViewModel before onCreate call.")
}
ensureViewModelStore()
return _viewModelStore!!
}
We see that the ensureViewModelStore
method is called, and then the _viewModelStore
field is returned.
// Lazily recreated from NonConfigurationInstances by val viewModelStore
private var _viewModelStore: ViewModelStore? = null
The _viewModelStore
field has no default value, so before returning it’s initialized inside the ensureViewModelStore
method:
private fun ensureViewModelStore() {
if (_viewModelStore == null) {
// Extract ComponentActivity#NonConfigurationInstances from Activity#getLastNonConfigurationInstance() method
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
// Restore ViewModelStore from NonConfigurationInstances
_viewModelStore = nc.viewModelStore
}
if (_viewModelStore == null) {
// Create ViewModelStore if there's no saved one inside NonConfigurationInstances object
_viewModelStore = ViewModelStore()
}
}
}
Here’s where it gets interesting. If the _viewModelStore
field is null
, first an attempt is made to get it from the getLastNonConfigurationInstance()
method, which returns an object of the NonConfigurationInstances
class.
If ViewModelStore
is also absent there, this can mean one of two things:
- The activity is being created for the first time and doesn’t have a saved
ViewModelStore
yet. - The system destroyed the application process (e.g., due to memory shortage), and then the user launched the app again, causing the
ViewModelStore
not to be preserved.
In either case, a new ViewModelStore
instance is created.
The most non-obvious part is the call to the getLastNonConfigurationInstance()
method. This method belongs to the Activity
class, and the NonConfigurationInstances
class, whose name alone looks intriguing, is declared in ComponentActivity
:
internal class NonConfigurationInstances {
var custom: Any? = null
var viewModelStore: ViewModelStore? = null
}
Thus, the NonConfigurationInstances
object is used to store ViewModelStore
during activity configuration changes. This allows preserving the ViewModel
state and restoring it after activity recreation.
The custom
variable defaults to null
and is practically not used, since ViewModelStore
more flexibly handles all the work of preserving states for surviving configuration changes. Nevertheless, the custom
variable can be used by overriding functions like onRetainCustomNonConfigurationInstance
and getLastCustomNonConfigurationInstance
. Before the appearance of ViewModel
, many developers actively used it (in 2012) to save data when recreating activity due to configuration changes.
The viewModelStore
variable has type ViewModelStore
and stores a reference to our ViewModelStore
object. The value in this NonConfigurationInstances#viewModelStore
variable is assigned when calling the onRetainNonConfigurationInstance
method, and extracted when calling getLastNonConfigurationInstance
(we already encountered this method above in the ensureViewModelStore
method).
Having understood the NonConfigurationInstances
class, let’s find out where an object of this class is created and how a value is assigned to the viewModelStore
field. For this, let’s turn to the onRetainNonConfigurationInstance
and getLastNonConfigurationInstance
methods, which are present in Activity
and ComponentActivity
. The method sources in ComponentActivity
look like this:
@Suppress("deprecation")
final override fun onRetainNonConfigurationInstance(): Any? {
// Maintain backward compatibility.
val custom = onRetainCustomNonConfigurationInstance()
var viewModelStore = _viewModelStore
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
viewModelStore = nc.viewModelStore
}
}
if (viewModelStore == null && custom == null) {
return null
}
val nci = NonConfigurationInstances()
nci.custom = custom
nci.viewModelStore = viewModelStore
return nci
}
The onRetainNonConfigurationInstance()
method returns a NonConfigurationInstances
object containing a reference to the previously created ViewModelStore
.
Thus, when the activity is destroyed (e.g., when the screen is rotated), this method is called, and the ViewModelStore
is saved in a NonConfigurationInstances
instance. When the activity is recreated, the NonConfigurationInstances
object is restored through the getLastNonConfigurationInstance()
method call, and the saved ViewModelStore
is extracted from it.
In the onRetainNonConfigurationInstance
method, logic is implemented to get the existing ViewModelStore
and Custom
object (if it exists). After getting these objects, they are placed in an instance of the NonConfigurationInstances
class, which is then returned from the method.
The onRetainNonConfigurationInstance
method creates an object of the NonConfigurationInstances
class, places the viewModelStore
and custom object inside, and then returns it. The question arises: who exactly calls this method?
The calling method inside the Activity class itself (the most basic Activity from which all others inherit):
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance(); // <- call to onRetainNonConfigurationInstance()
//...code
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity; // <- assignment of the extracted object from onRetainNonConfigurationInstance()
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
As we can see, the Activity
class itself calls the onRetainNonConfigurationInstance
method we met earlier and saves the result in the activity
field of the NonConfigurationInstances
class. At the same time, we encounter the NonConfigurationInstances
class again, but this time it’s declared in the Activity
itself and has additional fields:
static final class NonConfigurationInstances {
Object activity; // <- Here ComponentActivity.NonConfigurationInstances will be stored
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}
To eliminate confusion:
- The
ViewModelStore
object is stored insideComponentActivity#NonConfigurationInstances
. - The
ComponentActivity#NonConfigurationInstances
object itself is stored inActivity#NonConfigurationInstance
. - This is achieved through the
retainNonConfigurationInstances()
method of theActivity
class.
But who calls the retainNonConfigurationInstances()
method and where is the final Activity#NonConfigurationInstance
object stored that contains ViewModelStore
?
The answer to this question lies in the ActivityThread
class, which is responsible for managing activity lifecycles and their interaction with the system. This class handles creating, destroying, and recreating activities, as well as saving and restoring data during configuration changes.
The method from ActivityThread
that directly calls Activity.retainNonConfigurationInstances()
is called ActivityThread.performDestroyActivity()
.
Let’s examine its sources in the ActivityThread
class:
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
boolean getNonConfigInstance, String reason) {
//...
if (getNonConfigInstance) {
try {
// Call to Activity.retainNonConfigurationInstances()
// and save in r.lastNonConfigurationInstances
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to retain activity "
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
}
//...
}
To find the ActivityThread
sources, just use the search by class name in Android Studio: ActivityThread
. Or go to Android sources at one of the links:
After calling the retainNonConfigurationInstances()
method, the result is saved in the lastNonConfigurationInstances
field of the ActivityClientRecord
object:
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
The ActivityClientRecord
class represents an activity record and is used to store all information related to the actual activity instance. This is a kind of data structure for keeping track of activity during application execution.
Main fields of the ActivityClientRecord
class:
lastNonConfigurationInstances
—Activity#NonConfigurationInstance
object that storesComponentActivity#NonConfigurationInstances
which storesViewModelStore
.state
—Bundle
object containing saved activity state. Yes, this is the same Bundle we get in theonCreate
,onRestoreInstanceState
, andonSaveInstanceState
methods.intent
—Intent
object representing the activity launch intention.window
—Window
object associated with the activity.activity
— theActivity
object itself.parent
— parent activity (if any).createdConfig
—Configuration
object containing settings applied when creating the activity.overrideConfig
—Configuration
object containing current activity settings.
Within this article, we’re only interested in the lastNonConfigurationInstances
field, as it’s the one related to storing and restoring ViewModelStore
.
Now let’s understand how the performDestroyActivity()
method is called within the system call.
Call sequence:
ActivityTransactionItem.execute()
ActivityRelaunchItem.execute()
ActivityThread.handleRelaunchActivity()
ActivityThread.handleRelaunchActivityInner()
ActivityThread.handleDestroyActivity()
ActivityThread.performDestroyActivity()
It’s important to understand that at a higher level in this chain are classes like ClientTransactionItem, ClientTransaction, and ClientLifecycleManager, and even higher — the system itself, which manages device interaction with sensors and other components. However, we won’t go deeper into this chain, as just a couple of layers up we’ll find ourselves at the level of inter-process communication (IPC) and system work with processes.
At the top of the calls is the ActivityTransactionItem.execute()
method, which starts the chain: first calls getActivityClientRecord()
, and then that calls ClientTransactionHandler.getActivityClient()
.
public abstract class ActivityTransactionItem extends ClientTransactionItem {
@Override
public final void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
final ActivityClientRecord r = getActivityClientRecord(client, token); // <- Call to getActivityClientRecord
execute(client, r, pendingActions);
}
@NonNull
ActivityClientRecord getActivityClientRecord(
@NonNull ClientTransactionHandler client, IBinder token) {
final ActivityClientRecord r = client.getActivityClient(token); // <- getting client from ClientTransactionHandler(ActivityThread)
...
return r;
}
}
ClientTransactionHandler
is an abstract class, and one of its implementations is the ActivityThread
class we’ve already met.
public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
...
@Override
public ActivityClientRecord getActivityClient(IBinder token) {
return mActivities.get(token); // <- Returns from Map ActivityClientRecord by key
}
...
}
The ActivityRelaunchItem class inherits from ActivityTransactionItem and starts the handleRelaunchActivity
method on ActivityThread:
public class ActivityRelaunchItem extends ActivityTransactionItem {
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
...
client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
...
}
}
All launched activities within our application are stored in a Map collection in the ActivityThread
class object:
/**
* Maps from activity token to local record of running activities in this process.
* ....
*/
@UnsupportedAppUsage
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
Thus, we’ve finally figured out that our ViewModel
is actually stored in the ActivityThread
object, which is a singleton. Thanks to this, ViewModel
is not destroyed when configuration changes.
Important: The ActivityThread
instance is a singleton and exists throughout the entire application process lifecycle. In the handleBindApplication()
method inside ActivityThread
, an Application
object is created, which also lives until the process terminates. This means that ActivityThread
and Application
are linked by a common lifecycle, except that ActivityThread
appears earlier — before Application
creation — and manages its initialization.
ViewModelStore Restoration
Based on what we discovered earlier, the ViewModel
storage chain looks as follows:
ViewModel
is stored insideViewModelStore
.ViewModelStore
is stored inComponentActivity#NonConfigurationInstances
.ComponentActivity#NonConfigurationInstances
is stored inActivity#NonConfigurationInstances
.Activity#NonConfigurationInstances
is stored inActivityClientRecord
.ActivityClientRecord
is stored inActivityThread
.
When Activity
is recreated, its attach()
method is called, one of the parameters of which is Activity#NonConfigurationInstances
. This object is extracted from the ActivityClientRecord
associated with the Activity
.
When the Activity
’s configuration changes, the system immediately restarts it to apply new parameters. At this moment, ActivityThread.java
instantly extracts the ViewModelStore
, which is stored in ComponentActivity#NonConfigurationInstances
. This object, in turn, is located inside Activity#NonConfigurationInstances
.
Then Activity#NonConfigurationInstances
is saved in ActivityClientRecord
, associated with the recreated Activity
. Inside ActivityClientRecord
there is a special field lastNonConfigurationInstances
, where this object is placed. The ActivityClientRecord
itself is stored in a Map collection inside ActivityThread.java
, which is a singleton within the application process and can survive configuration changes.
After that, ActivityThread recreates the Activity, applying new configuration parameters. When creating it, it passes to it all saved data, including NonConfigurationInstances
, which ultimately contains ViewModelStore
. And ViewModelStore, in turn, stores our ViewModel.
Call Diagram for Saving and Restoring ViewModelStore
The diagram below illustrates the call chain. For simplification, some details are omitted, and redundant abstractions are removed:
Summary
In this article, we didn’t touch on the work of ViewModel as such — the focus was exclusively on why it doesn’t die when Activity is recreated, and what makes this possible at all.
We traced the entire chain: ViewModel
→ ViewModelStore
→ ComponentActivity#NonConfigurationInstances
→ Activity#NonConfigurationInstances
→ ActivityClientRecord
→ ActivityThread
. It is precisely in this deep nesting that the answer lies: ViewModel survives because it is not stored in Activity directly, but in an object that the system itself passes to the new Activity during configuration changes.
The ViewModelStore
itself is created either from scratch or restored via getLastNonConfigurationInstance()
. It is cleared only in onDestroy()
, if isChangingConfigurations == false
, — that is, if the Activity really dies, and is not recreated.
Under the hood, all this is provided by ActivityThread
, which saves NonConfigurationInstances
in ActivityClientRecord
, and then passes it to the attach()
method when creating a new Activity. ActivityThread
is a singleton, living as long as the process, and it is the reference point through which the entire recovery chain passes.
ViewModel survives not because someone “saves” it — but because no one kills it. As long as ActivityThread
is alive, ViewModelStore
is also alive.
Later we will return to ActivityThread
and ActivityClientRecord
again, this will be within the framework of the following articles.
Discussion
No comments yet. Be the first to share your thoughts!