What can we learn from the source code
- How to achieve a life cycle longer than a component?
- Data How do you save data when configuration changes such as screen rotation occur? (Note that configuration file changes and not all activity destruction save data)
- Why can memory leaks be avoided?
- Why is it different
Fragment
Use the sameActivity
Object to getViewModel
Can be easily implementedViewModel
Shared?
Android API 29
The use of the ViewModel
SunFlower (sunFlower) example, a simple demo is as follows:
class GardenActivity : AppCompatActivity(a){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_garden)
val model: MyViewModel by viewModels{
InjectorUtils.provideUserViewModelFactory()
}
// Get data
model.getUserInfo()
model.userInfo.observe(this, Observer<User>{ user ->
// update UI
})
}
}
object InjectorUtils {
// Inject data parameters
fun provideUserViewModelFactory(a): MyViewModelFactory {
val repository = UserRepository.getInstance()
return MyViewModelFactory(repository)
}
}
// ViewModel factory class
class MyViewModelFactory(
private val userRepository: UserRepository,) :ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(userRepository) as T
}
}
// ViewModel
class MyViewModel(private val userRepository: UserRepository) :ViewModel(a){
var userInfo: MutableLiveData<User> = MutableLiveData()
fun getUserInfo(a): LiveData<User>? {
return loadUserInfo()
}
private fun loadUserInfo(a): LiveData<User>? {
userInfo = userRepository.getUserInfo()
return userInfo
}
}
/ / User warehouse
class UserRepository {
fun getUserInfo(a):MutableLiveData<User>{
val user = MutableLiveData<User>()
user.value = User("Zhang"."18")
return user
}
companion object {
// For Singleton instantiation
@Volatile
private var instance: UserRepository? = null
fun getInstance(a) = instance ? :synchronized(this) { instance ? : UserRepository().also { instance = it } } } }/ / User entity
data class User(val name:String,val age:String)
Copy the code
The example above separates the data from the UI and let’s dive into the source code to see how it works with the first few questions.
(0) Creation of ViewModel
According to Jarry_Leo’s question, I went back to look at the source code.
The following three creation modes are available.
1 / / way
val model: MyViewModel by viewModels{
InjectorUtils.provideUserViewModelFactory()
}
// Method 2 is outdated and not recommended
// val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
3 / / way
// val model = ViewModelProvider(this,InjectorUtils.provideUserViewModelFactory()).get(MyViewModel::class.java)
Copy the code
From the creation of method 1, the inline noinline reified Lazy keyword is used
- The inline noinline see [throwing objects line] bosses to explain in detail: (zhuanlan.zhihu.com/p/224965169)
- Delegate let’s see: Kotlin delegate
- Check out the reified keyword in this article: Kotlin Study Notes – Generics and reified functions
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null): Lazy<VM> { val factoryPromise = factoryProducer ? : { defaultViewModelProviderFactory }return ViewModelLazy(VM::class.{ viewModelStore }, factoryPromise)
}
Copy the code
In the code above, inline and reified are used to get the class of the generic ViewModel type. The class is used to get the specific ViewModel by reflection. Noinline is used to solve the inline problem. To pass parameters from an inline function to another function, either the other function is also inline or the parameter is declared with noinline.
The code above finally returns Lazy
via ViewModelLazy
class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () - >ViewModelStore.private val factoryProducer: () - >ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get(a) {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
// Use the ViewModelProvider
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(a) = cached ! =null
}
// Get a concrete instance by reflection
@NonNull
@MainThread
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");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
Copy the code
We can see how the ViewModel is used to create objects in the component through the extension function viewModels.
The difference between method 1 and method 3 is that method 1 uses a Lazy function, which returns an object after it is created for the first time. That is to say, mode 1 is more or less better, but it is too late to prove how much or less.
(1) How to realize the life cycle longer than the component
As you can see from the diagram above, the life cycle of a ViewModel is longer than that of an Activity. So ViewModel how does that work?
In fact, LifeCycle library in Jetpack is primarily used, but of course you can do without it, and we’ll look at the implementation of that library in the next article.
LifeCycle library is a library that deals with the LifeCycle. That is, the library can sense the LifeCycle of an Activity and process the relevant logic at different times in the LifeCycle.
The above figure also shows that the ViewModel lifecycle is longer than the Activity onDestory lifecycle and has a method onCleared. Since this is after the Activity’s onDestory life cycle, let’s follow the source code to see how it handles it.
Compatactivity = compatactivity >FragmentActivity >ComponentActivity
The AppCompatActivity. Java only mandates events, and the specific processing logic needs to be processed in different Android API versions, which is beyond the scope of this article. You can search the Activity loading process for details@Override
protected void onDestroy(a) {
super.onDestroy(); getDelegate().onDestroy(); } FragmentActivity. Java handles the ON_DESTROY event@Override
protected void onDestroy(a) {
super.onDestroy(); mFragments.dispatchDestroy(); mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); } ComponentActivity.java constructor looks at the Lifecycle.Event.ON_DESTROY Event and gets the ViewModelStore call clear to clear the datapublic ComponentActivity(a) { Lifecycle lifecycle = getLifecycle(); . getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
The isChangingConfigurations method checks whether the configuration file has been modified and returns true if it has changed and false if it has not
// If the configuration file is modified, the data will not be cleared
if(! isChangingConfigurations()) {// Clear datagetViewModelStore().clear(); }}}}); }Copy the code
The ViewModel does listen for Lifecycle.Event.ON_DESTROY after the Activity’s onDestory to resolve the data cleanup problem in the ViewModel. And the source code has also made a judgment, in the page configuration file after modification will not clear data. This further demonstrates that the ViewModel can solve the problem of cleaning up data after modification of the page configuration file.
In terms of how to save and restore the data, we know that the page will be destroyed and rebuilt after the configuration file. The following two methods will be used in this process.
- onSaveInstanceState
- onRestoreInstanceState
So let’s look in the source code and see if there are any clues
(2) How to save data after page configuration modification
Compatactivity = compatactivity >FragmentActivity >ComponentActivity
AppCompatActivity. Java also just delegates events@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); getDelegate().onSaveInstanceState(outState); } FragmentActivity. Java handles saving data@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
markFragmentsCreated();
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
Parcelable p = mFragments.saveAllState();
if(p ! =null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) { requestCodes[i] = mPendingFragmentActivityResults.keyAt(i); fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i); } outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes); outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos); }} ComponentActivity. Java onSaveInstanceState method still does not have ViewModel, But then the method of onRetainNonConfigurationInstance comments, it isfinalSo can't rewritten, but it has a public onRetainCustomNonConfigurationInstance method, so that we can rewrite the method returns some Object Object, but this method is out of date. It is recommended that we use the ViewModel for this implementation.@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@Nullable
public final Object onRetainNonConfigurationInstance(a) {
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;
returnnci; } So we'll go back to the componentActivity.java constructor and find the Lifecycle.Event.ON_DESTROY, and there's a method getViewModelStore that gets ViewModel data, Its implementation is as follows:@NonNull
@Override
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) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = newViewModelStore(); }}return mViewModelStore;
}
Copy the code
If you do not use the ViewModel, you can use the following two methods to save and restore data during page destruction and reconstruction
- onSaveInstanceState
- onRestoreInstanceState
However, we know that these two methods can only store the data supported by the Bundle. To store other data, we need to use the following methods
- OnRetainNonConfigurationInstance: final so can’t rewrite, role is to save the data, call time is
onStop()
andonDestroy()
between - OnRetainCustomNonConfigurationInstance: @ Deprecated obsolete, it is recommended to use the ViewModel
- GetLastNonConfigurationInstance: role is to take out the data, call time is
onCreate()
after - GetLastCustomNonConfigurationInstance: @ Deprecated obsolete, it is recommended to use the ViewModel
With a ViewModel, you don’t have to worry about saving and recovering data when pages are destroyed and rebuilt.
The ViewModel will save the data in the onRetainNonConfigurationInstance method, as follows:
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; }}Copy the code
If the Activity rewrite the onRetainCustomNonConfigurationInstance method can also be custom Object type of Object stored in NonConfigurationInstances custom attributes. As follows:
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
Copy the code
It also determines whether the page is rebuilt during the Activity’s onDestory life cycle without deleting the data in the ViewModel
(3) Why can memory leaks be avoided?
Android API 29
We need to know why memory leaks in the first place. Objects with short life cycles are held by objects with long life cycles. As a result, objects with short life cycles are not reclaimed when they should be reclaimed, resulting in memory leaks. Such as:
Memory leaks caused by internal classes such as the non-static inner class Handler and the anonymous inner class Thread can result in a memory leak when a service process takes longer than the Activity lifetime
Because an inner class holds a reference to an external class by default, when an Activity is closed and needs to be reclaimed, its inner class still holds it, causing it not to be reclaimed.
The LiveData library was used in the use of the ViewModel at the beginning of this article. It is a life-aware data store class that ensures that LiveData updates only application component observers in an active lifecycle state.
Active life cycle state is when data is updated when it is visible to the user. Detailed implementation of the deep source code to view and write an article record.
Since the ViewModel automatically cleans up the data after onDestory, the combination can avoid memory leaks.
(4) Why can ViewModel share data between two or more fragments in an Activity?
Thanks Jarry_Leo for the guidance!
To create a ViewModel, you need to pass in this context, which is ViewModelStoreOwner. AppCompatActivity and Fragment implement ViewModelStoreOwner interface by default. So you can pass this directly in an Activity or Fragment.
After passing the context to the ViewModel, the HashMap in the ViewModelStore is used to fetch different ViewModels based on different keys. But the key here is androidx. Lifecycle. ViewModelProvider. DefaultKey: add the ViewModel class contains the full name of the path string.
The ViewModel in an Activity, on the other hand, persists until it is killed or shut down. This is why multiple fragments in an Activity can share data in the ViewModel.
The main code is as follows:
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param key The key to use to identify the ViewModel.
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if(viewModel ! =null) {
// TODO: log a warning.}}if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
Copy the code
conclusion
The ViewModel has the following functions:
- Store and manage interface-related data in a life-cycle oriented manner
- Allow data to persist after configuration changes such as screen rotation
- let
Activity
Of all theFragment
You can share data in the ViewModel - In combination with
LiveData
Use to avoid memory leaks
reference
- The ViewModel overview
- Android official architecture component ViewModel: From past life to trace the origin
- Android architecture component ViewModel source code analysis
- Android source code parsing -ViewModel
- ViewModel, ViewModel source code analysis, ViewModel how to ensure that the Activity after rebuilding to save data