Android
系统提供控件,比如Activity
和Fragment
,这些控件都是具有生命周期方法,这些生命周期方法被系统调用。在数据使用上有两个问题:
当这些控件被销毁或者被重建的时候,如果数据保存在这些对象中,那么数据就会丢失。比如在一个界面,保存了一些用户信息,当界面重新创建的时候,就需要重新去获取数据。当然了也可以使用控件自动再带的方法,在onSaveInstanceState
方法中保存数据,在onCreate
中重新获得数据,但这仅仅在数据量比较小的情况下。如果数据量很大,这种方法就不能适用了。
另外一个问题就是,经常需要在Activity
中加载数据,这些数据可能是异步的,因为获取数据需要花费很长的时间。那么Activity
就需要管理这些数据调用,否则很有可能会产生内存泄露问题。最后需要做很多额外的操作,来保证程序的正常运行。
同时Activity
不仅仅只是用来加载数据的,还要加载其他资源,做其他的操作,最后Activity
类变大,就是我们常讲的上帝类。也有不少架构是把一些操作放到单独的类中,比如MVP
就是这样,创建相同类似于生命周期的函数做代理,这样可以减少Activity
的代码量,但是这样就会变得很复杂,同时也难以测试。
ViewModel
是专门用于存放应用程序页面所需的数据。ViewModel将页面所需的数据从页面中剥离出来,页面只需要处理用户交互和展示数据。Viewmodel解决两个问题:
- 数据的变更与view隔离,也就是解耦
- 开发者不需要额外去关心因为屏幕旋转带来的数据恢复问题
ViewModel是一个抽象类,其中只有一个onCleared()
方法。当ViewModel不再被需要,即与之相关的Activity都被销毁时,onCleared()
方法会被系统调用。我们可以在该方法中执行一些资源释放的相关操作。(注意,由于屏幕旋转而导致的Activity重建,并不会调用该方法)
class TimerViewModel : ViewModel() {
private val timer: Timer = Timer() //定时器类
private var timeChangeListener: OnTimeChangeLister? = null
private var currentSecond: Int = 0
fun 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)
}
}
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
对象时会通过ViewModelProvider
的传递来绑定对应的声明周期。ViewModel
只有在Activity finish
或者Fragment detach
之后才会销毁。有时候一个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
使得在configuration change
(旋转屏幕等)保存数据变的十分方便,但是这不能用于应用被系统杀死时持久化数据。举个简单的例子,有一个界面展示用户信息。
不应该把整个用户信息放到SavedInstanceState
里,而是把单个用户对应的id
放到SavedInstanceState
,等到界面恢复时,再通过id
去获取详细的信息。这些详细的信息应该被存放在数据库中。
Jetpack ViewModel 并不等价于 MVVM ViewModel
这二者没有在同一个层次,MVVM ViewModel是MVVM架构中的一个角色,看不见摸不着只是一种思想。 而Jetpack ViewModel是一个实实在在的框架用于做状态托管,有对应的作用域可跟随Activity/Fragment生命周期,但这种特性恰好可以充当MVVM ViewModel的角色,分隔数据层和视图层并做数据托管。
所以结论是Jetpack ViewModel可以充当MVVM ViewModel 但二者并不等价
但jetpack的组件是可以分别使用的,所以可以先熟悉各个组件,然后往自己的项目里套。
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}.
*
* 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 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;
}
}
ViewModelProvider接收一个ViewModelStoreOwner对象作为参数。在以上示例代码中该参数是this,指代当前的Activity。这是因为我们的Activity继承自ComponentActivity,而在androidx依赖包中,ComponentActivity默认实现了ViewModelStoreOwner接口:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
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 NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore 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
的形式被缓存起来了。ViewModel与页面之间没有直接的关联,它们通过ViewModelProvider进行关联。当页面需要ViewModel时,会向ViewModelProvider索要,ViewModelProvider检查该ViewModel是否已经存在于缓存中,若存在,则直接返回,若不存在,则实例化一个。
因此,Activity由于配置变化导致的销毁重建并不会影响ViewModel,ViewModel是独立于页面而存在的。也正因为此,我们在使用ViewModel时,需要特别注意,不要向ViewModel中传入任何类型的Context或带有Context引用的对象,这可能会导致页面无法被销毁,从而引发内存泄漏。
在使用ViewModel时,不能将任何类型的Context或含有Context引用的对象传入ViewModel,因为这可能会导致内存泄漏。但如果你希望在ViewModel中使用Context,该怎么办呢?可以使用AndroidViewModel类,它继承自ViewModel,并接收Application作为Context。这意味着,AndroidViewModel的生命周期和Application是一样的,那么这就不算是一个内存泄漏了。
在上面的示例中,我们通过自定义接口OnTimeChangeListener来完成ViewModel与页面之间的通信。这不是一个好的方案。Jetpack为我们提供了LiveData组件来解决这个问题。通过LiveData,当ViewModel中的数据发生变化时,页面能自动收到通知,进而更新UI。