ViewModel的使用
1.需要先创建ViewModel类,继承自ViewModel重写onclear方法,使得页面销毁的时候能够走到自定义的onClear方法中
class MyViewModel : ViewModel() {//共享数据的核心在于拿到同一个LiveData实例,也就是拿到同一个ViewModel实例,其保存在ViewModelStore中//而ViewModelStore是Activity/Fragment提供的(做了屏幕转换的恢复处理,ViewModelStore会保存其数据)var progress:MutableLiveData<Int>?=nulloverride fun onCleared() {//页面销毁回调super.onCleared()Log.i("wwwwwwwwwwwwwwwww", "onCleared:MyVBiewModel cleared ")}
}
2 创建ViewModelProvider 在Activity中创建ViewModelProvider实例需要ViewModelOwner作为参数
和LifeCyclerOwner一样都是CommpentActivity实现的接口
除此之外还需要一个工厂。
该工厂默认实现是获取get函数传入的class反射创建ViewModel实例;也可以自定义工厂函数,会接受一个class的参数只需要返回该实例即可,中间的操作可以自定义
一,传入ViewModelOwner,Activity/Fragment已经实现该接口
ViewModelProvider(this)
.get(MyViewModel::class.java) //默认实现反射创建ViewModel实例二,创建实例过程自定义返回ViewModel实例即可
ViewModelProvider(this,object :ViewModelProvider.Factory{override fun <T : ViewModel?> create(modelClass: Class<T>): T {modelClass.constructorsreturn modelClass.getConstructor(Application::class.java).newInstance(application)}}).get(RoomViewModel::class.java).
上面是利用反射创建了一个带有参数的ViewModel。默认创建的是无参的实例
3.通过get传入对应的Viewmodel的Class对象即可。
简要分析
首先创建ViewModelProvider实例,看下对应源码:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {//第一个参数调用其getViewModelStore函数//第二个参数是个工厂稍后分析this(owner.getViewModelStore(), factory);}
可以看到Fragment和ComponentActivity都实现了该接口
接下来查看ComponentActivity。 1.首次mViewModelStore肯定为null,从nc中取出肯定也取不到只能通过new的方式去创建 2.当经历了屏幕旋转这时候就会从configure中取出viewmodelStore了,对应的也就是第二个红框,最后说明这个流程。
创建完ViewModelProvider后,调用get方法获取Viewmodel实例。
private static final String DEFAULT_KEY ="androidx.lifecycle.ViewModelProvider.DefaultKey";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");}//传入两个参数,//第一个是之后做缓存用的 DEFAULT_KEY 是常量//第二个是自定义ViewModel的classreturn get(DEFAULT_KEY + ":" + canonicalName, modelClass);}
@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {//首先从缓存中获取 viewmodelStore可以看成是一个map,保存ViewModelViewModel viewModel = mViewModelStore.get(key);//可以看到mFactory 分为两类:1.OnRequeryFactory 当缓存命中后该方法会回调并将命中的viewmodel传入2.KeyedFactory 继承自OnRequeryFactory 并提供create函数提供class创建实例过程有用户自定义if (modelClass.isInstance(viewModel)) {//OnRequeryFactory是缓存命中后的回调if (mFactory instanceof OnRequeryFactory) {((OnRequeryFactory) mFactory).onRequery(viewModel);}return (T) viewModel;} else {//noinspection StatementWithEmptyBodyif (viewModel != null) {// TODO: log a warning.}}//调用create函数将class传入,内部使用不同的构造方法创建实例并返回if (mFactory instanceof KeyedFactory) {viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);} else {viewModel = (mFactory).create(modelClass);}//缓存该ViewModelmViewModelStore.put(key, viewModel);return (T) viewModel;}
ViewModelStore源码:
public class ViewModelStore {
//可以看到就是个map缓存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();}
}
问答
如何实现旋转屏幕数据保持不变?
答:1.第一次创建 首先会从对应的Activity中的NoLastConfigure获取activity取出对应的ViewModelStore这个时候由于是第一次所以是null, 于是只能new一个ViewModelStore。
2.Activity重建时会销毁页面将ViewmodelStore保存到lastCongiure中并保存到ActivityClientRecord中传递给AMS端。
3.AMS重新调用(这里需要注意如果是配置引起的重建会走RelauchActivity而不是第一次普通的lauchActivity)ReLaunch会通过token取出对应的AcRecord,在attach的时候将record中上一次保存的lastCoinfigure取出来
4.onCreate的时候把store赋值给NoLastConfigure,这个时候页面执行onCreate获取 ViewModelStore就可以获取到了,而且Store是保存着这个页面的所有Viewmodel所以上一次的ViewModel中的数据还在并没有销毁
详细流程: HandlerRelauncherActivity中先调用handlerdestory销毁页面保存重要配置到record中(AMS会保存token{Activity唯一标识}和record的map),在调用lauchActivity重建页面通过token重新取出record,record在取出configure保存到新创建的activity的属性中。
1.当调用performDestory的时候创建一个Configure类取出viewmodelStore中如果没有直接取到从上一次的configure中取,创建完configure后保存到record中的lastConfigure属性中。
2.何时重建:在performLaunchActivity方法中调用attach方法,在这个方法中取出record中的lastConfigure赋值给成员变量mLastConfigure
和之前的Presenter有什么区别
个人感觉:
1.持有V层引用这个很好地解决了,但是回调V层还是得利用很多接口进行传递数据,这种主动通知V层的方式虽然变成接口回调的方式本质上耦合还是严重,可以通过LiveData V层去观察ViewModel中的数据变化这样耦合会降低一些
2.当配置失效比如屏幕旋转会销毁重建Activity,数据虽说可以通过onSavedInstance来传递,但是数据量并不能太大。但是ViewModel是系统原生支持的我们可以直接获取到上次销毁的ViewModel实例数据还在其中
3.具有生命周期可以自动管理防止泄漏,可通过onCleared告知持有该ViewModel的V层销毁
4.缓存命中和创建Viewodel都有回调,可以做自定义处理
原文链接:JetPack组件学习ViewModel - 掘金 (juejin.cn)