Jetpack AAC 系列文章
上一篇文章我们从使用案例和源码角度分析了Jetpack AAC Lifecycle组件。
Lifecycle在Jetpack AAC组件的金字塔结构中可以说是底层建筑的存在,将各个组件串联在一起,其重要性不言而喻,所以还是非常有必要了解Lifecycle在AAC架构组件中所扮演的角色。
没有看过的童鞋可以点击上面链接进行查看哦!
今天我们继续来了解LiveData和ViewModel,相信很多人看到LiveData和ViewModel首先联想到的就是MVVM架构。 那么LiveData和ViewModel在MVVM架构中是扮演怎样的角色呢?而MVVM中VM又是用于解决哪些问题?ViewModel又是如何搭配LiveData使用的呢?
本文将从上述几个问题出发,对LiveData和ViewModel进行分析讲解。
简介
首先我们来看看官方文档是怎样描述的。
LiveData
LiveData is a data holder class that can be observed within a given lifecycle.
* This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
* this observer will be notified about modifications of the wrapped data only if the paired
* LifecycleOwner is in active state
大致的意思是LiveData是一个带有生命周期且可被观察的数据持有类,这意味着它可以搭配Observer以及LifecycleOwner使用,只有当LifecycleOwner处于活跃状态时,观察者的才会被触发。
ViewModel
ViewModel is a class that is responsible for preparing and managing the data for an
{@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
It also handles the communication of the Activity /Fragment with the rest of the application
(e.g. calling the business logic classes).
从官方描述的可以看出ViewModel在Activity/Fragment中负责准备和管理数据,还可可以用来进行Activity/Fragment之间的通讯。
A ViewModel is always created in association with a scope (an fragment or an activity) and will
* be retained as long as the scope is alive. E.g. if it is an Activity, until it is
* finished.
ViewModel一般是在Activity/Fragment中进行创建,只要Activity一直存在,ViewModel也会一直保留。当我们在Activity中使用ViewModel时,直至Activity被标记或者真实关闭后,ViewModel才会被销毁。
MVVM
上图是官方在Jetpack应用架构指南中给出的MVVM架构中各个模块交互的工作流程图,每个组件仅依赖于其下一级的组件。 而VM在MVVM中的职责就是负责视图模型层与M层/V层进行交互。Respostitory在这里可以理解为M层,为数据提供层,当M层数据有变更时,由VM层来通知V层Activity/Fragment中数据内容变更,UI层在自行根据内容进行UI刷新。
注:ViewModel只负责数据管理和业务逻辑相关的工作,不涉任何和UI相关的操作。ViewModel只专注于处理业务数据以及与M层的数据请求逻辑,UI刷新操作则交给自己的上一级去操作。 每一层都有每一层所关注的内容和责任,这样才能达到真正解耦,也是名副其实的"分手大师"。
案例
假设我们有一个用户页面,需要拉取用户的信息进行填充展示,看看用LiveData和ViewModel如何实现。
ViewModel
public class UserViewModel extends ViewModel {
private MutableLiveData<User> userMutableLiveData;
public MutableLiveData<User> getUser() {
if (userMutableLiveData == null) {
userMutableLiveData = new MutableLiveData<>();
userMutableLiveData.setValue(null);
}
return userMutableLiveData;
}
public void setUserMutableLiveData(User user) {
userMutableLiveData.setValue(user);
}
public void loadUser() {
User user = new UserModel().getUserById("10001");
setUserMutableLiveData(user);
}
}
V层
public class UserActivity extends AppCompatActivity {
private static final String TAG = "UserActivity";
TextView tvUserName;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
tvUserName = findViewById(R.id.tv_user_name);
UserViewModel userViewModel = new ViewModelProvider(UserActivity.this).get(UserViewModel.class);
userViewModel.getUser().observe(this, new Observer<User>() {
@Override
public void onChanged(User user) {
Log.e(TAG, "onChanged: user.name" + user.name);
tvUserName.setText(user.name);
}
});
userViewModel.loadUser("1003");
}
}
M层
public class UserModel {
public User getUserById(String id) {
return new User(id, "张三");
}
}
User
public class User implements Serializable {
public String id;
public String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
打开页面后的输出结果如下:
UserActivity E/onChanged: user.name 张三
从上述简单的调用例子可以看出,ViewModel实例化创建是依赖于ViewModelProvider。
new ViewModelProvider(UserActivity.this).get(UserViewModel.class);
注:ViewModelProviders的绑定方式已经废弃了,androidx新版本推荐是采用new ViewModelProvider的形式
observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
在V层我们通过ViewModel拿到的User类MutableLiveData对象,在添加观察时需要添加一个LifecycleOwner实现类,而这个接口实现类我们在上篇讲解lifecycle有说到,Activity和Frament就是LifecycleOwner的实现类,所以在LiveData中持有LifecycleOwner就可以进行生命周期的监听。
注:MutableLiveData数据变更提供了两个方法,一个是
postValue(T value),一个是setValue(T value)。postValue方法是在非UI线程调用,setValue方法在UI线程中调用
LiveData的更多用用法
自定义LiveData
LiveData除了可以在外部调用observe进行监听,还可以自定义实现MutableLiveData在内部监听onActive和onInactive两个函数。
public class CustomLiveData extends MutableLiveData<String> {
private static final String TAG = "CustomLiveData";
@Override
protected void onActive() {
super.onActive();
Log.e(TAG, "onActive: LiveData进入活跃状态");
}
@Override
protected void onInactive() {
super.onInactive();
Log.e(TAG, "onActive: LiveData进入不活跃状态");
}
}
既然能够监听到活跃状态,我们试着把地图定位的逻辑放到LiveData中是怎样的:
public class LocationLiveData extends MutableLiveData<BDLocation> {
private LocationClient locationClient;
private InternalListener internalListener;
public Context context;
public LocationLiveData(Context context) {
this.context = context;
}
@Override
protected void onActive() {
super.onActive();
locationClient = new LocationClient(context);
locationClient.registerLocationListener(internalListener = new BDLocationListener() {
@Override
public void onReceiveLocation(BDLocation bdLocation) {
setValue(bdLocation);
}
});
locationClient.setLocOption(getLocationClientOption());
}
@Override
protected void onInactive() {
super.onInactive();
if (locationClient != null) {
locationClient.unRegisterLocationListener(internalListener);
locationClient.stop();
}
}
}
可以看到,当LiveData处于活跃时,我们自动开启定位。当不活跃时,我们自动关闭定位,除了可以自管理定位的生命周期,还能及时切断与其他层的关联,防止内存泄露。
userViewModel.getLocation().observe(this, new Observer<BDLocation>() {
@Override
public void onChanged(BDLocation bdLocation) {
}
});
observeForever
liveData还未我们提供一个不关联生命周期的观察者监听,对观察者来说LiveData会一直活跃状态。
userViewModel.getLocation().observeForever(new Observer<BDLocation>() {
@Override
public void onChanged(BDLocation bdLocation) {
}
});
关于LiveData的使用就介绍这么多,如有遗漏的点后续再进行补充,最后我们来总结下引入LiveData的好处:
-
自管理生命周期(告别手工处理的胶水代码)
-
保证数据实时性(数据集中管理,一处刷新,可多处同步,还可用于模块通信)
-
拒绝内存泄露(绑定生命周期,切端与其他模块的关联)
-
搭配ViewModel使用(可处理Activity/Fragment的异常销毁恢复情况)
ViewModel通讯
从官方文档描述我们看到,ViewModel除了负责准备和管理数据,还可以进行Activity/Frgment之间的通讯,我们通过下面的例子来看看利用ViewModel是如何进行通讯交互的
public class UserFragmentActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "UserFragmentActivity";
UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_user);
userViewModel = new ViewModelProvider(UserFragmentActivity.this).get(UserViewSaveModel.class);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "第一次拉取 user.name");
userViewModel.loadUser("1001");
}
});
}
@Override
public void onClick(View v) {
Log.e(TAG, "第一次拉取 user.name");
userViewModel.loadUser("1001");
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart" );
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause" );
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume" );
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy" );
}
}
我们在xml文件中加载两个fragment
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="拉取用户数据" />
<fragment
android:name="com.waylenw.adr.mvvm.vm.fragment.UserFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="fragment1" />
<fragment
android:name="com.waylenw.adr.mvvm.vm.fragment.UserFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="fragment2" />
</LinearLayout>
Fragment
public class UserFragment extends Fragment {
private static final String TAG = "UserFragment";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return LayoutInflater.from(getContext()).inflate(R.layout.fragment_user, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
super.onViewCreated(view, savedInstanceState);
TextView tvUserName = view.findViewById(R.id.tv_user_name);
UserViewSaveModel userViewModel = new ViewModelProvider(getActivity()).get(UserViewSaveModel.class);
userViewModel.getUser().observe((LifecycleOwner) UserFragment.this, new Observer<User>() {
@Override
public void onChanged(User user) {
if (user == null) {
Log.e(TAG, getTag() + " onChanged:" + null);
return;
}
Log.e(TAG, getTag() + " onChanged: user.name" + user.name);
Log.e(TAG, getTag() + " onChanged: user" + user);
tvUserName.setText(user.name);
}
});
tvUserName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, getTag() + "重新设置 user.name");
userViewModel.getUser().getValue().name = "李四";
userViewModel.setUserMutableLiveData(userViewModel.getUser().getValue());
}
});
}
打开页面后,通过按钮点击进行User的第一次加载,输出内容如下:
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null
com.waylenw.adr.mvvm E/UserFragmentActivity: 第一次拉取 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
然后我们在其中的一个Fragmnet中进行点击,将用户名称更改为李四
com.waylenw.adr.mvvm E/UserFragment: fragment1重新设置 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
可以发现,两个Fragmnet共享了同一个ViewModel,所以当注册观察者时,获取到的是同一个数据源,User@30b4c83并没有发生变更
ViewModel的生命周期
上图是官方给出的ViewModel生命周期变化。当Activity旋转时,此时Activity重走生命周期,但是ViewModel并未跟随重新创建,只有页面真正标记关闭时,ViewModel才会重新创建。所以ViewModel中的数据并不会不会被清楚。ViewModel还帮我处理了Activity异常销毁时的数据保存工作。
我们也不用手动在onSaveInstanceState()和onRestoreInstanceState()/onCreate()函数中来保存和恢复数据了。
以下我们通过两个场景来测试一下ViewModel生命周期。
旋转屏幕的表现
还是我们刚才测试ViewModel的Demo,此时我们进行一次旋屏操作,看看输出的结果是怎样的:
com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
从日志输出中可以看到LiveData中所引用的user对象没有发生变更,当Activity从非活跃状态切换到活跃状态时,会自动触发一次监听回调给到观察者,所以ViewModel的声明生命周期是伴随Activity finish销毁而销毁。
页面销毁恢复
这个场景,我们通过设置=>开发者模式=>不保留活动,即用户离开后销毁当前这个页面。
模拟的输出结果如下:
com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null
Activity已经进行销毁重新创建了,两个Fragment中拿到数据也是null,很明显数据并没有保留下来。 那ViewModel如何才能在页面销毁恢复的情况下把数据进行保存呢?答案使用是SavedStateHandle。SavedStateHandle该如何使用呢?我们把刚才ViewModel进行改造一下
public class UserViewSaveModel extends ViewModel {
public static final String SAVE_KEY = "SAVE_KEY";
private MutableLiveData<User> userMutableLiveData;
private SavedStateHandle savedStateHandle;
public UserViewSaveModel(SavedStateHandle savedStateHandle) {
this.savedStateHandle = savedStateHandle;
}
public MutableLiveData<User> getUser() {
if (userMutableLiveData == null) {
userMutableLiveData = new MutableLiveData<>();
if (savedStateHandle.contains(SAVE_KEY)) {
userMutableLiveData.setValue(savedStateHandle.get(SAVE_KEY));
} else {
userMutableLiveData.setValue(null);
}
}
return userMutableLiveData;
}
public void setUserMutableLiveData(User user) {
userMutableLiveData.setValue(user);
savedStateHandle.set(SAVE_KEY, user);
}
public void loadUser(String userId) {
User user = new UserModel().getUserById(userId);
setUserMutableLiveData(user);
}
}
UserFragment替换为UserViewSaveModel
public class UserFragment extends Fragment {
private static final String TAG = "UserFragment";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return LayoutInflater.from(getContext()).inflate(R.layout.fragment_user, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView tvUserName = view.findViewById(R.id.tv_user_name);
UserViewSaveModel userViewModel = new ViewModelProvider(getActivity()).get(UserViewSaveModel.class);
userViewModel.getUser().observe((LifecycleOwner) UserFragment.this, new Observer<User>() {
@Override
public void onChanged(User user) {
if (user == null) {
Log.e(TAG, getTag() + " onChanged:" + null);
return;
}
Log.e(TAG, getTag() + " onChanged: user.name" + user.name);
Log.e(TAG, getTag() + " onChanged: user" + user);
tvUserName.setText(user.name);
}
});
tvUserName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, getTag() + "重新设置 user.name");
userViewModel.getUser().getValue().name = "李四";
userViewModel.setUserMutableLiveData(userViewModel.getUser().getValue());
}
});
}
}
UserFragmentActivit也替换为UserViewSaveModel
public class UserFragmentActivity extends AppCompatActivity {
private static final String TAG = "UserFragmentActivity";
UserViewSaveModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
setContentView(R.layout.activity_fragment_user);
userViewModel = new ViewModelProvider(UserFragmentActivity.this).get(UserViewSaveModel.class);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "第一次拉取 user.name");
userViewModel.loadUser("1001");
}
});
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy");
}
}
我们在来看看实际的运行效果
1.初始化+点击加载
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragmentActivity: 第一次拉取 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
- 返回桌面进入应用进入后台,在切回到应用中
com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/ResMng NATIVE_MSG_FILTER: endActivityTransaction: margin state not match
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@484f3f0
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@484f3f0
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
当页面恢复后Activity的中用的户昵称是我们最后设置的用户张三,并没有像之前未使用SavedStateHandle的例子中出现数据丢失的情况。
通过上述两个例子,可以论证ViewModel只有当页面finish真实关闭时,才会跟随销毁,所以当我们旋转屏幕时,页面重走生命周期时,ViewModel并没有重新创建。在ViewModel中我们使用SavedStateHandle进行数据保存后,页面销毁恢复后,数据也能够相应的被还原。那么在这过程中SavedStateHandle替我们做了哪些事情呢?
下一篇文章我们将从ViewModel的源码来分析讲解SavedStateHandle是如何在页面销毁时完成保存数据的操作。
End
注:Jetpack AAC架构组件系列后续还会继续分析详解,觉得本文对你有帮助,更多实用内容正在更新中....