The ViewModel background
The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation.
Excerpt from ViewModel Overview
Specifically, the ViewModel has the following characteristics:
- For simple data, the onSaveInstanceState() method can be used when an Activity is destroyed to recover its bound data from onCreate, but not for large amounts of data, such as user lists or bitmaps. The Viewmodel supports large amounts of data and does not require serialization or deserialization.
- View controllers often require asynchronous calls that may take some time for the process to return. The interface controller needs to manage these calls and ensure that the system cleans them up after they are destroyed to avoid potential memory leaks. The Viewmodel is a good way to avoid memory leaks.
- If the interface controller is also responsible for loading data from the database or network, the class becomes bloated. Assigning too much responsibility to an interface controller can cause a single class to try to handle all the work of the application itself, rather than delegating it to another class. The Viewmodel effectively separates the view data logic from the view controller.
As you can see, the VIewmodel stores and manages view-related data in the form of an awareness lifecycle.
The basic use
With a preliminary understanding of the Viewmodel’s characteristics, let’s start with a simple operation.
Custom data retrieval classes
class TestRepository {
suspend fun getNameList(a): List<String> {
return withContext(Dispatchers.IO) {
listOf("Ha ha"."Ha ha")}}}Copy the code
Custom ViewModel inherits ViewMode to implement custom ViewModel.
class TestViewModel: ViewModel() {
private val nameList = MutableLiveData<List<String>>()
val nameListResult: LiveData<List<String>> = nameList
private val testRepository = TestRepository()
fun getNames(a) {
viewModelScope.launch {
nameList.value = testRepository.getNameList()
}
}
}
Copy the code
MutableLiveData is created and updated with the setVale method of MutableLiveDat.
You can then use TestViewModel in your Activity.
class MainActivity : AppCompatActivity() {
// Create ViewModel 1
// Create the ViewModel with the Kotlin delegate feature
/ / need to add dependent on implementation 'androidx. Activity: activity - KTX: 1.2.3'
// viewModels() is also created inside the ViewModel by creating ViewModel 2
private val mainViewModel: TestViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate (savedInstanceState)
setContentView(R.layout.activity_main)
// Create ViewModel 2
val mainViewModel = ViewModelProvider(this).get(TestViewModel::class.java)
mainViewModel.nameListResult.observe(this, {
Log.i("MainActivity"."mainViewModel: nameListResult: $it")
})
mainViewModel.getNames()
}
}
Copy the code
The ViemodelProvider is used to retrieve the TestViewModel object and to observe its changes with LiveData. Through log printing we get:
MaiinViewModel: nameListResult: [ha, ha, ha]Copy the code
When we rotated the phone again, the printed message was still there.
MaiinViewModel: nameListResult: [ha, ha, ha]Copy the code
Even if MainActivity is rebuilt, the ViewModel instance object still exists, and the internal LiveData has not changed.
How can the ViewModel recover data after the Activity rotates the screen? Now let’s analyze its source code.
Source code analysis
The creation of the Viewmodel
First, let’s look at how the Viewmodel is created.
From the above we can see that the Viewmodel is created as follows:
val mainViewModel = ViewModelProvider(this).get(TestViewModel::class.java)
Copy the code
You pass in the class type, and you get the ViewModel. Take a look at the constructor of ViewModelProcider:
# # #ViewModelProvider
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
// Get the ViewModelStore
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
// The ViewModerStore is cached
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
Copy the code
So here we see he’s got the ViewmodelStore. It could be that all we’re passing in is an instance of the Activity, and what’s the ViewModelStoreOwner?
// Have the ViewModelStore scope.
// The implementation of this interface is responsible for retaining the ViewModelStore it owns during configuration changes,
// And call viewModelStore.clear () when the scope will be destroyed.
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore(a);
}
Copy the code
ViewModelStoreOwner is an interface. Let’s see if ComponentActivity implements it:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory.SavedStateRegistryOwner.OnBackPressedDispatcherOwner {...// Implements ViewModelStoreOwner and overwrites getViewModelStore
public ViewModelStore getViewModelStore(a) {
if (getApplication() == null) {
// The contents of the file are analyzed below.returnmViewModelStore; }}Copy the code
ComponentActivity implements the ViewModelStoreOwner interface.
Back to the example, ViewModelProvider (this). The get (TestViewModel: : class. Java) of the get method is done?
### ViewModelProvider
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
// Get the key, which is the Map in ViewmodelStore used to store the ViewModel key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// Get the ViewModel instance
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
// Return the cached ViewModel object
return (T) viewModel;
} else {
if(viewModel ! =null) {}}// Use factory mode to create ViewModel instance
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
// Put the created ViewModel instance into the mViewModelStore cache
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
Copy the code
MViewModelStore = mViewModelStore = mViewModelStore = mViewModelStore = mViewModelStore
### ViewModelStore
public class ViewModelStore {
// Internally stored by HashMap
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(a) {
return new HashSet<>(mMap.keySet());
}
// Call the viewModel clear method and clear the viewModel
public final void clear(a) {
for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
So in ViewModelStore we do that by storing the viewModel as a value in a HashMap.
So having seen how the VIewModel is created, and where the instance is cached, let’s look at how the ViewModelStore is created.
The creation of ViewmodelStore
Let’s go back to the getViewModelStore method above:
# # #ComponentActiivty
public ViewModelStore getViewModelStore(a) {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
/ / get NonConfigurationInstances
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null) {
/ / from NonConfigurationInstances ViewModelStore recovery
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = newViewModelStore(); }}return mViewModelStore;
}
Copy the code
First obtained from the NonConfigurationInstances VIewModelStore examples, if not, will create a new ViewModelSotre. Here we knew, create ViewModelStore will cache the NonConfigurationInstances. And when did the ViewModelStore get cached?
Look at getLastNonConfigurationInstance method:
### Activity
/ / previous by onRetainNonConfigurationInstance () returns the object
/**
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
* {@link #onStart} calls to the new instance, allowing you to extract
* any useful dynamic state from the previous instance.
*
* <p>Note that the data you retrieve here should <em>only</em> be used
* as an optimization for handling configuration changes. You should always
* be able to handle getting a null pointer back, and an activity must
* still be able to restore itself to its previous state (through the
* normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
* function returns null.
*
* <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
* {@link Fragment#setRetainInstance(boolean)} instead; this is also
* available on older platforms through the Android support libraries.
*
* @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
*/
public Object getLastNonConfigurationInstance(a) {
returnmLastNonConfigurationInstances ! =null
? mLastNonConfigurationInstances.activity : null;
}
Copy the code
Through the above comments can know by onRetainNonConfigurationInstance mLastNonConfigurationInstances is returned.
And when the called onRetainNonConfigurationInstance? We finally found it:
# # #ActivityThread
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {...if (getNonConfigInstance) {
try {
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); }}}... }Copy the code
Called in the performDestroyActivity method, Can see onRetainNonConfigurationInstance () method returns the Object will be assigned to ActivityClientRecord lastNonConfigurationInstances, like this is preserved. Look at the onRetainNonConfigurationInstance method.
### ComponetActivity
// Called when the configuration has changed
// Preserve all appropriate non-configured states
public final Object onRetainNonConfigurationInstance(a) {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so let's see if there are any existing ViewModelStores in our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null) { viewModelStore = nc.viewModelStore; }}if (viewModelStore == null && custom == null) {
return null;
}
/ / create NonConfigurationInstances object and assignment viewModelStore
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
Copy the code
Here we knew, the Activity of the reconstruction for configuration changes destroyed when calls onRetainNonConfigurationInstance will save to NonConfigurationInstances ViewModelStore instance. After reconstruction can be used when getLastNonConfigurationInstance method to get the cache before ViewModelStore instance.
NonConfigurationInstances what, how can still remain in the destruction of reconstruction? Look at NonConfigurationInstances stored in where, where were the first to look at the assignment.
# # #Activity
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) {...// Assign is done in the attach methodmLastNonConfigurationInstances = lastNonConfigurationInstances; . }Copy the code
MLastNonConfigurationInstances is the Activity of the attach assignment method.
# # #ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } · · · · · ·// Obtained from ActivityClientRecord
activity.attach(appContext, 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.configCallback, r.assistToken, r.shareableActivityToken); . }Copy the code
In the Activity of performLaunchActivity approach, can see in ActivityClientRecord lastNonConfigurationInstances is kept.
And since the interface calls the performDestroyActivity method at destruction time, Internal and invokes the Activity lastNonConfigurationInstances cache to ActivityClientRecord retainNonConfigurationInstances method, is put into the process of the application itself.
So the page at the time of destruction of reconstruction, save lastNonConfigurationInstances in ActivityClientRecord is not affected.
Here we also know why the ViewModel still exists after the Activity is rebuilt due to configuration changes.
The destruction of the ViewModel
We already know that the ViewModle clear method is called in the ViewmodelStore.
###ViewModelStore
public class ViewModelStore {...public final void clear(a) {
for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
When does the viewModel store clear method get called in order to iterate through the cache?
# # #ComponentActivity
public ComponentActivity(a) {... getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
// The activity life cycle is in destory state
if (event == Lifecycle.Event.ON_DESTROY) {
// The non-configuration has changed
if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); }Copy the code
Here we see that the condition for calling the Clear method in ViewModelStore is called first by observing that the current lifecycle is in DESTORY, and also by detecting that the Configurations are destroyed because of a change.
So when the activity is destroyed, determine that getViewModelStore().clear() is called only if the destruction was not caused by a configuration change.
ViewModelScope understand?
ViewModelScope is a Kotlin extension property of the ViewModel. It can exit when the ViewModel is destroyed (onCleared() method is called). And when did it close?
Let’s take a look at its implementation:
//CoroutineScope is bound to the ViewModel. When the ViewModel cleared, this range will be cancelled, namely call / / ViewModel. The onCleared the range bound to Dispatchers. Main. Immediate
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if(scope ! =null) {
return scope
}
// A missed buffer is added to the ViewModel via setTagIfAbsent()
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close(a) {
coroutineContext.cancel()
}
}
Copy the code
As you can see from the comments, the Viewmoel is removed from scope when the onClear method of the ViewModel is called. And why does clearing the ViewModel shut it down?
Let’s look at the Viewmodel clear method again:
### ViewModel
// This method is called when the ViewModel is no longer used and destroyed.
protected void onCleared(a) {}final void clear(a) {
mCleared = true;
// Since clear() is final, this method is still called on the mock object, and mBagOfTags are empty in these cases. It is always empty
// Since setTagIfAbsent and getTag are not final, we can skip clearing them
if(mBagOfTags ! =null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
Copy the code
How is the onClear method empty? Now that this method is called by the clear method, what is mbagofTags?
private final Map<String, Object> mBagOfTags = new HashMap<>();
Copy the code
This is a Map object, and if you look further down you can see that setTagIfAbsent is assigned.
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
// Put it into map if it has not been saved before
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
//// If this viewModel is marked clear
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
Copy the code
The setTagIfAbsent method is called when the viewModelScope is first created, which is where the object is added to the ViewModel.
To review, the first time viewModelScope is called, setTagIfAbsent(JOB_KEY, CloseableCoroutineScope) is used for caching.
Look at the CloseableCoroutineScope class, which implements the Closeable interface and cancels the coroutine scoped coroutineContext object in close().
conclusion
We first introduced the background of viewModel, to understand its characteristics: due to configuration changes interface destruction and reconstruction still exist, do not hold UI applications, also introduced the basic use of viewModel. Finally, a large section of the ViewModel analysis of several key core principles.
This is the end of our analytical journey
reference
The ViewModel website
Android-viewmodel summary