ViewModel
ViewModel is an important component in Android Jetpack. Its advantage is that it has a life cycle as shown in the figure below and will not be destroyed due to changes in Activity configuration such as screen rotation. It is an important foundation for implementing UI state management in the MVVM architecture.
class HogeActivity : AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate")
val activity: FragmentActivity = this
val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()
// Activity由于横竖品切换销毁重建,此处的viewModel 仍然是重建前的实例
val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
// 如果直接new实例则会创建新的ViewModel实例
// val viewModel = FooViewModel()
Log.d(TAG, " - Activity :${
this.hashCode()}")
Log.d(TAG, " - ViewModel:${
viewModel.hashCode()}")
}
}
The log of the above code when switching between horizontal and vertical screens is as follows:
#Activity初次启动
onCreate
- Activity :132818886
- ViewModel:249530701
onStart
onResume
#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
- Activity :103312713 #Activity实例不同
- ViewModel:249530701 #ViewModel实例相同
onStart
onResume
The following code is the key to ensure that the ViewModel is not destroyed when the screen is switched. Let's take a look at the source code for the entrance in turn
val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
ViewModelProvider
ViewModelProvider source code is very simple, hold one ViewModelProvider.Factory
and ViewModelStore
instance respectively
package androidx.lifecycle;
public class ViewModelProvider {
public interface Factory {
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
...
}
get()
Return ViewModel instance
package androidx.lifecycle;
public class ViewModelProvider {
...
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
...
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
...
}
The logic is very clear:
- ViewModelProvider obtains ViewModel through ViewModelStore
- If the acquisition fails, create a ViewModel through ViewModelProvider.Factory
ViewModelStore
package androidx.lifecycle;
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);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
It can be seen that ViewModelStore is a right Map
package.
val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
The above code ViewModelProvider() passed in in the construction parameter 1 FragmentActivity
(the base class is ComponentActivity
) is actually ViewModelStoreOwner
an implementation.
package androidx.lifecycle;
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelStore in ViewModelProvider is from ViewModelStoreOwner.
public class ViewModelProvider {
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
this.mViewModelStore = store;
}
FragmentActivity#getViewModelStore()
FragmentActivity
Implements ViewModelStoreOwner
the getViewModelStore
method
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {
private ViewModelStore mViewModelStore;
@NonNull
@Override
public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}
...
}
By getLastNonConfigurationInstance()
acquiring NonConfigurationInstances
instance to get real viewModelStore
, getLastNonConfigurationInstance()
what is it?
Activity#getLastNonConfigurationInstance()
package android.app;
public class Activity extends ContextThemeWrapper implements ... {
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.
From official documents, we know that through the onRetainNonConfigurationInstance()
returned Activity instance before the screen is rotated, it can be getLastNonConfigurationInstance()
obtained after the screen is rotated , so the key to not destroying the screen before and after rotation isonRetainNonConfigurationInstance
Activity#onRetainNonConfigurationInstance()
#Activity初次启动
onCreate
- Activity :132818886
- ViewModel:249530701
onStart
onResume
#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
- Activity :103312713 #Activity实例不同
- ViewModel:249530701 #ViewModel实例相同
onStart
onResume
When the screen rotation, onRetainNonConfigurationInstance()
in onStop
and onDestroy
between calls
package android.app;
public class Activity extends ContextThemeWrapper implements ... {
public Object onRetainNonConfigurationInstance() {
return null;
}
...
}
onRetainNonConfigurationInstance
There is only empty implementation in Activity, FragmentActivity
which is rewritten in
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}
...
}
FragmentActivity returns the NonConfigurationInstances instance storing the ViewModelStore through onRetainNonConfigurationInstance().
It is worth mentioning that it onRetainNonConfigurationInstance
provides a hook opportunity:, onRetainCustomNonConfigurationInstance
allows us to make custom objects not destroyed like ViewModel
NonConfigurationInstances will be attach
passed by the system to the newly rebuilt Activity in:
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)
Then in onCreate
, by getLastNonConfigurationInstance()
obtaining ViewModelStore in NonConfigurationInstances
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {
private ViewModelStore mViewModelStore;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
mViewModelStore = nc.viewModelStore;
}
...
}
}
Recap
Activity starts for the first time
- FragmentActivity#onCreate() is called
- At this time, the mViewModelStore of FragmentActivity is still null
- HogeActivity's onCreate() is called
- ViewModelProvider instance creation
- FragmentActivity#getViewModelStore() is called, mViewModelStore is created and assigned
Screen rotation occurs
- FragmentActivity#onRetainNonConfigurationInstance() is called
- NonConfigurationInstances instance holding mViewModelStore is returned
Activity reconstruction
- FragmentActivity#onCreate() is called
- Get NonConfigurationInstances instance from Activity#getLastNonConfigurationInstance()
- The mViewModelStore of FragmentActivity before the screen rotation is saved in NonConfigurationInstances, and it is assigned to the mViewModelStore of FragmentActivity after reconstruction
- HogeActivity#onCreate() is called
- Get ViewModel instance through ViewModelProvider#get()