Android Jetpack组件系列文章:
Android Jetpack组件(一)LifeCycle
Android Jetpack组件(二)Navigation
Android Jetpack组件(三)ViewModel
Android Jetpack组件(四)LiveData
Android Jetpack组件(五)Room
Android JetPack组件(六)DataBinding
Android Jetpack组件(七)Paging
Android Jetpack组件(八)WorkManager
Android Jetpack组件(九)DataStore
Android Jetpack组件(十)App Startup
Android Jetpack组件(十一)Compose
首语
对于支持横竖屏切换的应用程序,我们切换横竖屏时,Activity
会被重新创建,我们需要考虑数据的存储和恢复。Jetpack为我们提供了ViewModel组件帮我们解决这个问题,ViewModel以注重生命周期的方式存储和管理界面相关的数据。ViewModel独立于配置变化,就算Activity
重建,也不会影响ViewModel的生命周期。
在应用开发中,通常将UI交互、数据获取等业务逻辑全部写在页面中,当项目需求不断增加,页面功能复杂时,页面类会显得尤为臃肿,且不宜维护。这样做也违背了“单一功能原则”,页面只应该负责处理用户与UI控件的交互及数据展示,获取数据的业务逻辑应该单独处理。
Android提供了ViewModel类专门用于存放应用程序页面所需的数据,它可以理解为视图与数据模型的桥梁,使视图与数据分离开同时也保持通信。
ViewModel与onSaveInstanceState()
通常我们使用onSaveInstanceState()
来解决屏幕旋转带来的数据丢失问题,但是它只能保存少量的支持序列化的数据,Viewmodel支持页面中的所有数据。需要注意的是,ViewModel不支持数据的持久化,当界面彻底销毁时,ViewModel及所持有的数据就不存在了,onSaveInstanceState()
没有这个限制,可以持久化页面的数据,两者用途不一。
依赖
//包含了 viewmodel 和 livedata,lifecycle-extensions 中的 API 已弃用
//implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
//或者指明使用viewmodel
implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
使用
首先自定义一个ViewModel,继承ViewModel类。
public class HomeViewModel extends ViewModel {
@Override
protected void onCleared() {
super.onCleared();
}
}
ViewModel是一个抽象类,只有一个onClear()
,当ViewModel与之关联的Activity
都被销毁时,该方法就会被调用,在方法里可以执行一些资源释放相关的工作,注意屏幕旋转导致的Activity
重建不会调用该方法。
我们通过一个计数器的例子来展示ViewModel的使用,将视图与数据分离。
private int value;
public String gtValue() {
return String.valueOf(value);
}
public void addValue() {
value+=1;
if (onChangedListener != null) {
onChangedListener.onChanged(String.valueOf(value));
}
}
/**
* 通过接口的形式将数据传递出去,更好的方式是通过LiveData,
*/
public interface onChangedListener {
void onChanged(String value);
}
private onChangedListener onChangedListener;
public void setOnChangeListener(onChangedListener onChangeListener) {
this.onChangedListener = onChangeListener;
}
然后在Activity
中,每点击一次,计数器+1,ViewModel的实例化是通过ViewModelProvider
来完成的,它会判断ViewModel是否存在,若存在直接返回,不存在则创建。
HomeViewModel homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
textView.setText(homeViewModel.gtValue());
button.setOnClickListener(view -> homeViewModel.addValue());
homeViewModel.setOnChangeListener(textView::setText);
运行代码可以发现点击计数器都会+1,同时旋转屏幕导致Activity重建时,数据不会消失,代表ViewModel并没有被销毁,持有的数据一直存在。
Fragment间共享数据
Activity
中的两个或更多 Fragment
经常需要相互通信,这种情况处理比较复杂,ViewModel能将数据从Activity
中剥离处理,只要Activity
不销毁,ViewModel就一直存在,基于这些特性,多个Fragment
可以使用其 Activity
范围共享 ViewModel 来处理此类通信。
//ViewModelProvider的范围必须是所在activity
HomeViewModel homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
在另一个Fragment
中同样实例化ViewModel。当切换Fragment
时,会提示计数器当前值,达到了Fragment
之间的通信。
homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Toast.makeText(getActivity(), homeViewModel.gtValue(),Toast.LENGTH_SHORT).show();
}
在Fragment
中使用ViewModel和Activity
中使用类似。
ViewModel的原理
ViewModel接收一个ViewModelStoreOwner
对象作为参数,我们传递this,这是因为Activity
继承ComponentActivity
,它默认实现了ViewModelStoreOwner
接口。源码如下。
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
实现此接口的职责是在配置更改期间保留拥有的ViewModelStore
,并在该作用域将要被销毁时调用。
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
public ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
@NonNull
@Override
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;
}
}
getViewModelStore()
返回的类型是ViewModelStore
,从源码可以看出,ViewModel是以HashMap<String,ViewModel>
的形式缓存起来了,就像之前说的,页面需要ViewModel时,先判断缓存中是否存在,若存在,直接返回,不存在则创建。
Fragment
也默认实现了ViewModelStoreOwner
接口,原理和Activity
类似。
需要注意的是,实例化ViewModel时不要传入任何类型的Context
或带有Context
引用的对象,将引发内存泄漏。
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();
}
}
AndroidViewModel
如果实例化ViewModel要传递Context
对象,可以使用AndroidViewModel
类,它继承自ViewModel,并接收Application
作为Context
,因此它的生命周期和应用Application
的生命周期一样,不会导致内存泄漏,同时可以处理特定场景的数据问题。