How ViewModel survive in configuration changed
In https://developer.android.com/topic/libraries/architecture/viewmodel, the first line mentioned that
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
Let find out how it work underhood
Lifecycle on Rotation
From the diagram above, we can see that viewmodel are persist through the destroy and recreation of the activity.
I written a small demo associate to the scenario above to validate whether it is still the same viewmodel
that persist the rotation:
1 | class MainActivity : AppCompatActivity() { |
And now I will perform a rotation on my device to check the log:
1 | D/MainActivityTADA: onSaveInstanceState: me.chkfung.tada.MainActivity@bd87c29 |
From the Log result, we can see that it is the same viewmodel object although the activity is different already.
There come a question, from the first line of the demo code, the viewmodel is actually declared in my Activity class, how does it persist the same object when the activity itself is no longer the same?
So I started to make some wild guess:
- Is the viewModel obtained from a static object?
- If yes, is this static object bound to global scope or activity scope?
- Activity Scope - But the activity is destroyed already, how does this scope work?
- Global Scope - Seems valid, but if there are multiple activity with the same class, how do there know which viewmodel to restore? Maybe by using a timestamp Key?
- If No, is this viewModel created from a outer layer?
- If yes, is this static object bound to global scope or activity scope?
Digging into the code
Initializing our viewModel
init
1 | val model: FirstViewModel by viewModels() |
goto viewModels()
1 | /** |
- if we have a factory for our viewModel creation, we can pass in the factory, else default
- In this Initialize constructor, it will create a
Lazy
ViewModel that will not be created until it is call usingget()
- VM:class : our ViewModel Class
- { viewModelStore } : obtained from ComponentActivity inner classes
- factoryPromise : our ViewModel Factory
goto ViewModelLazy
1 | /** |
get()
, in this function, it will return the viewModel if it is created and stored incached
, else it will perform 3:- Create the viewModel using the
ViewModelProvider
and assign tocached
Craetion and Obtain of ViewModel
goto ViewModelProvider(store, factory).get(viewModelClass.java)
1 | public class ViewModelProvider { |
- Passing default_key + canonical class name and Class to another get function
- Obtain viewModel from
ViewModelProvider.mViewModelStore
- OnRequeryFactory allow us to have a callback when this function triggered again,
androidx/lifecycle/ViewModelProvider.java 1
2
3
4static class OnRequeryFactory {
void onRequery( ViewModel viewModel){
}
} - when this function triggered again means that
ViewModelLazy
being destroy by scenario like : rotation
- OnRequeryFactory allow us to have a callback when this function triggered again,
- return viewModel if
mViewModelStore
contains it - else create it
- put viewModel into
mViewModelStore
After digging for several layer, we can see that ViewModelStore
is a very important component that retain our viewModel,
Retaining ViewModelStore and ViewModel
goto { viewModelStore }
1 | public class ComponentActivity extends androidx.core.app.ComponentActivity implements |
- Check if mViewModelStore already lazily initialize, if yes, direct return
- Tried to obtain
getLastNonConfigurationInstance
before rotation - if
lastNonConfigurationInstance
exist, obtain the viewModelStore in it - if not exist, create a new
ViewModelStore
Interesting, there is a lastNonCongigurationInstance
that persist the viewModelStore
! Let’s go deeper on this
goto getLastNonConfigurationInstance
1 | public class Activity extends ContextThemeWrapper |
- return
mLastNonConfigurationInstances
- initialize
mLastNonConfigurationInstances
fromActivity.attach
So now we know that mLastNonConfigurationInstances
is passing through the attach function on Activity
, but who is calling this function? As our knowledge, we know that ActivityThread
is the one responsible for the creation of it.
goto ActivityThread.performLaunchActivity
1 |
|
- lastNonConfigurationInstances is calling activity.attach in
ActivityThread.performLaunchActivity
- lastNonConfigurationInstances is a member of
ActivityClientRecord
- Instance of
ActivityClientRecord
is created inActivityThread.startActivityNow
and pass toActivityThread.performLaunchActivity
- Instance of
- after launching it, it will store this
r : AcitivityCliendRecord
intoActivityThread.mActivities
- When activity configurationChanged / rotated, it will try to get
ActivityClientRecord
from a local mapmActivities
- Passing this
ActivityClientRecord
that containslastNonConfigurationInstances
back to the recreated activity.
Overall Flow Diagram
Addition knowledge:
- ViewModelStore is a hashMap key value pair
androidx/lifecycle/ViewModelStore.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45/**
* Class to store {@code ViewModels}.
* <p>
* An instance of {@code ViewModelStore} must be retained through configuration changes:
* if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
* changes, new instance of an owner should still have the same old instance of
* {@code ViewModelStore}.
* <p>
* If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
* then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
* be notified that they are no longer used.
* <p>
* Use {@link ViewModelStoreOwner#getViewModelStore()} to retrieve a {@code ViewModelStore} for
* activities and fragments.
*/
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);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}