Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二)
2020/6/19 23:25:40
本文主要是介绍Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Jetpack AAC 系列文章
- Android Jetpack之AAC Lifecycle你用对了吗(一)
- Android Jetpack之AAC 最佳拍档之LiveData+ViewModel(二)
上一篇文章我们从使用案例和源码角度分析了Jetpack AAC Lifecycle组件。
Lifecycle在Jetpack AAC组件的金字塔结构中可以说是底层建筑的存在,将各个组件串联在一起,其重要性不言而喻,所以还是非常有必要了解Lifecycle在AAC架构组件中所扮演的角色。
没有看过的童鞋可以点击上面链接进行查看哦!
今天我们继续来了解LiveData和ViewModel,相信很多人看到LiveData和ViewModel首先联想到的就是MVVM架构。 那么LiveData和ViewModel在MVVM架构中是扮演怎样的角色呢?而MVVM中VM又是用于解决哪些问题?ViewModel又是如何搭配LiveData使用的呢?
本文将从上述几个问题出发,对LiveData和ViewModel进行分析讲解。
简介
我们先来看看官方是怎样描述的。
关于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除了可以在外部调用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 复制代码
通过上述两个例子,可以论证ViewModel只有当页面finish真实关闭时,才会跟随销毁,所以当我们旋转屏幕时,页面重走生命周期时,ViewModel并没有重新创建。在ViewModel中我们使用SavedStateHandle进行数据保存后,页面销毁恢复后,数据也能够相应的被还原。那么在这过程中SavedStateHandle替我们做了哪些事情呢?
下一篇文章我们将从ViewModel的源码来分析讲解SavedStateHandle是如何在页面销毁时完成保存数据的操作。
End
注:Jetpack AAC架构组件系列后续还会继续分析详解,觉得本文对你有帮助,可以扫描下面公众号二维码图片关注,更多实用内容正在更新中....
这篇关于Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-01-18android.permission.read_media_video
- 2024-01-18android_getaddrinfo failed eai_nodata
- 2024-01-18androidmo
- 2024-01-15Android下三种离屏渲染技术
- 2024-01-09Android 蓝牙使用
- 2024-01-06Android对接华为AI - 文本识别
- 2023-11-15代码安全之代码混淆及加固(Android)
- 2023-11-10简述Android语音播报TTS
- 2023-11-06Android WiFi工具类
- 2023-07-22Android开发未来的出路