文章目录
- ViewModel
- 实现ViewModel
- ViewModel的生命周期
- 在Fragments间分享数据
- ViewModel和SavedInstanceState对比
- ViewModel原理
- ViewModel与AndroidViewModel
ViewModel
Android
系统提供控件,比如Activity
和Fragment
,这些控件都是具有生命周期方法,这些生命周期方法被系统调用。在数据使用上有两个问题:
-
当这些控件被销毁或者被重建的时候,如果数据保存在这些对象中,那么数据就会丢失。比如在一个界面,保存了一些用户信息,当界面重新创建的时候,就需要重新去获取数据。当然了也可以使用控件自动再带的方法,在
onSaveInstanceState
方法中保存数据,在onCreate
中重新获得数据,但这仅仅在数据量比较小的情况下。如果数据量很大,这种方法就不能适用了。 -
另外一个问题就是,经常需要在
Activity
中加载数据,这些数据可能是异步的,因为获取数据需要花费很长的时间。那么Activity
就需要管理这些数据调用,否则很有可能会产生内存泄露问题。最后需要做很多额外的操作,来保证程序的正常运行。
同时Activity
不仅仅只是用来加载数据的,还要加载其他资源,做其他的操作,最后Activity
类变大,就是我们常讲的上帝类。也有不少架构是把一些操作放到单独的类中,比如MVP
就是这样,创建相同类似于生命周期的函数做代理,这样可以减少Activity
的代码量,但是这样就会变得很复杂,同时也难以测试。
ViewModel
是专门用于存放应用程序页面所需的数据。ViewModel将页面所需的数据从页面中剥离出来,页面只需要处理用户交互和展示数据。Viewmodel解决两个问题:
- 数据的变更与view隔离,也就是解耦
- 开发者不需要额外去关心因为屏幕旋转带来的数据恢复问题
实现ViewModel
ViewModel是一个抽象类,其中只有一个onCleared()
方法。当ViewModel不再被需要,即与之相关的Activity都被销毁时,onCleared()
方法会被系统调用。我们可以在该方法中执行一些资源释放的相关操作。(注意,由于屏幕旋转而导致的Activity重建,并不会调用该方法)
class TimerViewModel : ViewModel() {private val timer: Timer = Timer() //定时器类private var timeChangeListener: OnTimeChangeLister? = nullprivate var currentSecond: Int = 0fun start() {timer.schedule(timerTask {currentSecond++timeChangeListener?.onTimeChanged(currentSecond)}, 1000, 1000)}override fun onCleared() {super.onCleared()timer.cancel()}fun setOnTimeChangeListener(listener: OnTimeChangeLister) {timeChangeListener = listener}interface OnTimeChangeLister {fun onTimeChanged(second: Int)}
}
- ViewModel的实例化过程,是通过ViewModelProvider来完成的。
ViewModelProvider
会判断ViewModel是否存在,若存在则直接返回,否则它会创建一个ViewModel。
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val tv = findViewById<TextView>(R.id.tv)val model = ViewModelProvider(this).get(TimerViewModel::class.java)//获得ViewModel实例model.setOnTimeChangeListener(object : TimerViewModel.OnTimeChangeLister {override fun onTimeChanged(second: Int) {runOnUiThread {tv.text = second.toString()}}})model.start()}
}
运行程序并旋转屏幕。当旋转屏幕导致Activity重建时,计时器并没有停止。这意味着,横/竖屏状态下的Activity所对应的ViewModel是同一个,它并没有被销毁,它所持有的数据也一直都存在着。
ViewModel的生命周期
ViewModel
在获取ViewModel
对象时会通过ViewModelProvider
的传递来绑定对应的声明周期。ViewModel
只有在Activity finish
或者Fragment detach
之后才会销毁。
在Fragments间分享数据
有时候一个Activity
中的两个或多个Fragment
需要分享数据或者相互通信,这样就会带来很多问题,比如数据获取,相互确定生命周期。
使用ViewModel
可以很好的解决这个问题。假设有这样两个Fragment
,一个Fragment
提供一个列表,另一个Fragment
提供点击每个item
现实的详细信息。
public class SharedViewModel extends ViewModel {private final MutableLiveData<Item> selected = new MutableLiveData<Item>();public void select(Item item) {selected.setValue(item);}public LiveData<Item> getSelected() {return selected;}
}public class MasterFragment extends Fragment {private SharedViewModel model;public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);//以上官方文档写法,已被弃用,采取以下写法:ViewModel viewModel = new ViewModelProvider(getActivity()/this).get(*ViewModel.class);itemSelector.setOnClickListener(item -> {model.select(item);});}
}public class DetailFragment extends LifecycleFragment {public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);//以上官方文档写法,已被弃用,采取以下写法:ViewModel viewModel = new ViewModelProvider(getActivity()/this).get(*ViewModel.class);ViewModel viewModel = new ViewModelProvider(getActivity()/this).get(*ViewModel.class);model.getSelected().observe(this, { item ->// update UI});}
}
两个Fragment
都是通过getActivity()
来获取ViewModelProvider
。这意味着两个Activity
都是获取的属于同一个Activity
的同一个ShareViewModel
实例。
这样做优点如下:
Activity
不需要写任何额外的代码,也不需要关心Fragment
之间的通信。Fragment
不需要处理除SharedViewModel
以外其他的代码。这两个Fragment
不需要知道对方是否存在。Fragment
的生命周期不会相互影响
ViewModel和SavedInstanceState对比
ViewModel
使得在configuration change
(旋转屏幕等)保存数据变的十分方便,但是这不能用于应用被系统杀死时持久化数据。举个简单的例子,有一个界面展示用户信息。
不应该把整个用户信息放到SavedInstanceState
里,而是把单个用户对应的id
放到SavedInstanceState
,等到界面恢复时,再通过id
去获取详细的信息。这些详细的信息应该被存放在数据库中。
Jetpack ViewModel 并不等价于 MVVM ViewModel
这二者没有在同一个层次,MVVM ViewModel是MVVM架构中的一个角色,看不见摸不着只是一种思想。 而Jetpack ViewModel是一个实实在在的框架用于做状态托管,有对应的作用域可跟随Activity/Fragment生命周期,但这种特性恰好可以充当MVVM ViewModel的角色,分隔数据层和视图层并做数据托管。
所以结论是Jetpack ViewModel可以充当MVVM ViewModel 但二者并不等价
但jetpack的组件是可以分别使用的,所以可以先熟悉各个组件,然后往自己的项目里套。
ViewModel原理
val model = ViewModelProvider(this).get(TimerViewModel::class.java)
首先看ViewModelProvider的源码:
public class ViewModelProvider {private final Factory mFactory;private final ViewModelStore mViewModelStore;/*** Creates {@code ViewModelProvider}. This will create {@code ViewModels}* and retain them in a store of the given {@code ViewModelStoreOwner}.* <p>* This method will use the* {@link HasDefaultViewModelProviderFactory#getDefaultViewModelProviderFactory() default factory}* if the owner implements {@link HasDefaultViewModelProviderFactory}. Otherwise, a* {@link NewInstanceFactory} will be used.*/public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory(): NewInstanceFactory.getInstance());}//比较常用的构造函数public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}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);}//获得ViewModel实例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 StatementWithEmptyBodyif (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;}}
ViewModelProvider接收一个ViewModelStoreOwner对象作为参数。在以上示例代码中该参数是this,指代当前的Activity。这是因为我们的Activity继承自ComponentActivity,而在androidx依赖包中,ComponentActivity默认实现了ViewModelStoreOwner接口:
public class ComponentActivity extends androidx.core.app.ComponentActivity implementsLifecycleOwner,ViewModelStoreOwner,SavedStateRegistryOwner,OnBackPressedDispatcherOwner {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 (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstancesmViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}return mViewModelStore;}
}
public interface ViewModelStoreOwner {/*** Returns owned {@link ViewModelStore}** @return a {@code ViewModelStore}*/@NonNullViewModelStore getViewModelStore();
}
ViewModelStore类的代码是:
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());}/*** 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();}
}
从ViewModelStore的源码可以看出,ViewModel实际上是以
HashMap<String,ViewModel>
的形式被缓存起来了。ViewModel与页面之间没有直接的关联,它们通过ViewModelProvider进行关联。当页面需要ViewModel时,会向ViewModelProvider索要,ViewModelProvider检查该ViewModel是否已经存在于缓存中,若存在,则直接返回,若不存在,则实例化一个。
因此,Activity由于配置变化导致的销毁重建并不会影响ViewModel,ViewModel是独立于页面而存在的。也正因为此,我们在使用ViewModel时,需要特别注意,不要向ViewModel中传入任何类型的Context或带有Context引用的对象,这可能会导致页面无法被销毁,从而引发内存泄漏。
ViewModel与AndroidViewModel
在使用ViewModel时,不能将任何类型的Context或含有Context引用的对象传入ViewModel,因为这可能会导致内存泄漏。但如果你希望在ViewModel中使用Context,该怎么办呢?可以使用AndroidViewModel类,它继承自ViewModel,并接收Application作为Context。这意味着,AndroidViewModel的生命周期和Application是一样的,那么这就不算是一个内存泄漏了。
在上面的示例中,我们通过自定义接口OnTimeChangeListener来完成ViewModel与页面之间的通信。这不是一个好的方案。Jetpack为我们提供了LiveData组件来解决这个问题。通过LiveData,当ViewModel中的数据发生变化时,页面能自动收到通知,进而更新UI。