introduce
A ViewModel is something between a View and a Model. It acts as a bridge, allowing views and data to be separated and still communicate.
The ViewModel strips the data needed for the page from the page, which only needs to handle user interaction and present the data.
Viewmodels typically notify the page of changes in data and update the UI through either LiveData or DataBinding
ViewModel lifecycle
Viewmodels are always created with the Activity/Fragment being created and destroyed with the Activity/Fragment being destroyed
As you can see from this ViewModel lifecycle, the ViewModel is not destroyed when the screen rotates and the Activity is destroyed and recreated, which helps us retrieve data to update the UI after the Activity is recreated. It has an advantage over onSaveInstanceState because onSaveInstanceState is not suitable for recovery operations with a large amount of data. It can only recover a small amount of data that is serialized and deserialized, whereas ViewModel supports not only a large amount of data, Serialization and deserialization operations are not required.
Basic usage
Add dependencies:
Create a ViewModel classes
class LiveDataViewModel:ViewModel() { private var timer: Timer? = null lateinit var mListener: OnTimeChangeListener var currentSecond = 0 fun startTiming(){ if(timer==null){ timer = Timer() timer? .schedule(object :TimerTask(){override fun run() {currentSecond ++ mlistener.onchanged (currentSecond)}},0,1000)}} fun setTimeListener(listener: OnTimeChangeListener){ this.mListener = listener } override fun onCleared() { super.onCleared() timer? .cancel() } interface OnTimeChangeListener{ fun onChanged(second:Int) } }Copy the code
Create the ViewModel in the activity and call the functions inside it to update the data
class MainActivity : AppCompatActivity() { var binding: ActivityMainBinding ? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding=DataBindingUtil.setContentView(this,R.layout.activity_main) val bean = User("arrom", 18); binding? .user = bean; val model = ViewModelProvider(this)[LiveDataViewModel::class.java] model.setTimeListener(object :LiveDataViewModel.OnTimeChangeListener{ override fun onChanged(second: Int) { Log.d("MainActivity","onChanged${second}") bean.name = "arrom-${second}" bean.notifyChange() } }) Binding?.clickBtn?.setonClickListener {model.startTiming() log.d ("MainActivity"," button click event ")}}Copy the code
Source code analysis
val model = ViewModelProvider(this)[LiveDataViewModel::class.java]
Copy the code
Enter the ViewModelProvider class
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner))
Copy the code
*/
public open class ViewModelProvider(
private val store: ViewModelStore,
private val factory: Factory
)
Copy the code
To create a ViewModelProvider, you must pass in two parameters:
-
The ViewModelStore is used to store and manage viewModels
-
Factory is used to instantiate the viewModel
The ViewModelProvider provides methods instantiated in 3:
- Simple factory pattern construction
- Construct an instance by reflection
- Implement the Factory construct instance yourself
NewInstanceFactory construct instance
@Suppress("SingletonConstructor") public open class NewInstanceFactory : Factory { @Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T {return try {// return modelclass.newinstance ()} catch (e: InstantiationException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } } public companion object { private var sInstance: NewInstanceFactory? = null @JvmStatic public val instance: NewInstanceFactory @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) get() { if (sInstance == null) { sInstance = NewInstanceFactory() } return sInstance!! }}}Copy the code
When we construct an instance using NewInstanceFactory(), we can see from the source code that it implements the Factory interface and calls the newInstance method of the ViewModel we passed in the previous GET method in its internal create method
AndroidViewModelFactory construct instance
public open class AndroidViewModelFactory( private val application: Application ) : NewInstanceFactory() { @Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T {return if (AndroidViewModel: : class. Java. IsAssignableFrom (modelClass)) {try {/ / by reflection for instance objects modelClass.getConstructor(Application::class.java).newInstance(application) } catch (e: NoSuchMethodException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InstantiationException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InvocationTargetException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } } else super.create(modelClass) } public companion object { internal fun defaultFactory(owner: ViewModelStoreOwner): Factory = if (owner is HasDefaultViewModelProviderFactory) owner.defaultViewModelProviderFactory else instance internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey" private var sInstance: AndroidViewModelFactory? = null @JvmStatic public fun getInstance(application: Application): AndroidViewModelFactory { if (sInstance == null) { sInstance = AndroidViewModelFactory(application) } return sInstance!! }}}Copy the code
Implement the Factory construct instance yourself
public interface Factory { public fun <T : ViewModel> create(modelClass: Class<T>): T } public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T { var viewModel = store[key] if (modelClass.isInstance(viewModel)) { (factory as? OnRequeryFactory)? .onRequery(viewModel) return viewModel as T } else { @Suppress("ControlFlowWithEmptyBody") if (viewModel ! = null) { // TODO: log a warning. } } viewModel = if (factory is KeyedFactory) { factory.create(key, modelClass) } else { factory.create(modelClass) } store.put(key, viewModel) return viewModel }Copy the code
When we construct the ViewModelProvider, we save the two parameters we pass in. When we call the get method, the method first retrieves the ViewModel from the ViewModelStore by key. This key is the DEFAULT_KEY + class name above. If found, return directly, otherwise call factory create method to create ViewModel instance into ViewModelStore for management and save, and return the instance
Enter the ViewModelStore class
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());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
Copy the code
The ViewModel is managed using a HashMap
ViewModelStoreOwner analysis
When we get the ViewModelStore, we get it through a ViewModelStoreOwner interface
When we create a ViewModelProvider, we pass in either a fragment or an activity, and the fragment is actually the activity’s getViewModelStore method
ComponentActivity class
public ViewModelStore getViewModelStore() { 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 the current component's mViewModelStore is empty, To get if from NonConfigurationInstances (mViewModelStore = = null) {NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; In NonConfigurationInstances} / / if not found, Create a new ViewModelStore and save it if (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }Copy the code
GetViewModelStore to get a ViewModelStore, when ViewModelStore is empty, Will call getLastNonConfigurationInstance method to restore NonConfigurationInstances before state changes, and this method is the corresponding onRetainNonConfigurationInstance, This method is called by the system to save data information when the component state changes.
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
Copy the code
The timing of the viewModelStore assignment
public final Object onRetainNonConfigurationInstance() { 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; return nci; }Copy the code
In the state of the components is changed, will try to get viewModelStore, and to save viewModelStore in NonConfigurationInstances and return.
In onRetainNonConfigurationInstance to save viewModelStore in getLastNonConfigurationInstance viewModelStore for recovery
summary
When we instantiate the ViewModelProvider in an activity, the dependent activity internally calls the getViewModelStore method to get a ViewModelStore, or creates one if it doesn’t, and saves it. Then call the viewModelProvider.get method to save our ViewModel instance through the ViewModelStore. When our activity state changes, such as rotating the screen, The system calls onRetainNonConfigurationInstance method, in this way will our ViewModelStore to save.