浅谈ViewModel
1 主要功能
- Activity、Fragment存活期间的数据存储;
- bind同一Activity的多个Fragment间数据共享;
- 独立或与LiveData配合实现代码解耦;
2 使用方法
1) 引入ViewModel
- 在build.gradle中添加编译lifecycle.extensions module,该module同时包含ViewModel和LiveData:
compile('android.arch.lifecycle:extensions:1.0.0')
由于lifecycle.extensions内部依赖26.1.0版本的support_v7包,建议将工程中已引用的support_v7组件版本也升级到26.1.0或以上。
2) 构造数据对象
- 自定义ViewModel类,继承ViewModel;
- 在自定义的ViewModel类中添加需要的数据对象;
public class DemoViewModel extends ViewModel {
final public DemoData mData = new DemoData();
public DemoViewModel() {}
}
3) 获取数据
- 通过ViewModelProviders和Activity / Fragment获取ViewModelProvider;
- 通过ViewModelProvider和自定义ViewModel类获取自定义ViewModel对象;
- 从自定义ViewModel对象获取数据对象,进行需要的读写操作。
DemoData data = ViewModelProviders.of(getActivity())
.get(DemoViewModel.class)
.mData;
data.doSth();
3 使用场景示范
1) 单Activity多Fragment间数据维护
- 需求点:
- bind同一个Activity的Fragment A、B;
- Fragment A、B间存在跳转关系;
- Fragment A、B共同维护一些数据,且A、B均有读取、修改的业务需求;
- 传统实现方法1:Intent +onFragmentResult()
大致流程:
- 跳转时需要将数据按k-v封装入Bundle,或者将数据Parcelable传递给下个页面;
- 下个页面修改数据并返回后,需要从onFragmentResult()的Intent解析并同步数据;
- Intent传递的数据大小总量不能超过1M;
麻烦且数据大小有限制。
- 传统实现方法2:数据对象SingleInstance、static
缺点:
- 数据的生命周期要自行控制;
- 容易内存泄漏;
同样麻烦,且有一定风险。
ViewModel同时规避了传统方法的缺点:
- bind同一个Activity的Fragments均可以通过ViewModelProvider获取共同的数据对象,无需主动进行数据传递;
- 脱离Intent、Bundle、Parcelable这些用起来很麻烦的控件;
- 数据生命周期由ViewModel内部掌控,无需手动管理销毁;
2) 与LiveData配合实现UI、业务逻辑分层
同为官方架构组件的LiveData,功能是监听数据变化,并回调给注册的observer。ViewModel+LiveData可以很方便的抽象出数据层和业务层,快速解耦。 下面的Demo来自官方案例。通过Demo,以及LiveData、ViewModel同处一个module,可以看出官方非常建议两者搭配使用。再配合以往的Data-Binding,可以快速搭建起一套简易的MVVM业务体系。
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if(users == null) {
users= new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
//Do an asyncronous operation to fetch users.
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
//Create a ViewModel the first time the system calls an activity's onCreate()method.
//Re-created activities receive the same MyViewModel instance created by thefirst activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this,users -> {
//update UI
});
}
}
4 生命周期特性
ViewModel的生命周期与Lifecycle同步,当Activity /Fragment超出Lifecycle范围(并不是onDestroy()回调),ViewModel连同其包含的数据一起被销毁了。具体有多大呢,按照官方说法:
in the case of an activity, when it finishes, whilein the case of a fragment, when it’s detached.
Activity退栈后(Fragment则是与Activity解除关系后),ViewModel就解除引用关系,准备被系统回收了。下面这张图片更直观的展示了ViewModel的生命周期:
5 源码分析
带着两个小问题简单的进行下源码分析:
1) ViewModel与Activity / Fragment的映射关系是如何建立起来的?
(以Activity为例)
第一部分:获取ViewModelProvider
- 调用ViewModelProviders.of(Activity);
- 一路调用,最终走到HolderFragmentManager.holderFragmentFor(Activity);
- HolderFragmentManager依次从FragmentManager和HolderFragmentManager持有的HashMap查找:该Activity是否有HolderFragment与之映射(HolderFragment的作用下面一个问题将谈到);
HolderFragment.class
HolderFragment holderFragmentFor(FragmentActivity activity) {
FragmentManagerfm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm);
if(holder != null) {
return holder;
}else {
holder = (HolderFragment)this.mNotCommittedActivityHolders.get(activity);
if(holder != null) {
return holder;
}else {
//创建HolderFragment
- 有则返回该HolderFragment;没有则创建一个HolderFragment:
- 新建一个HolderFragment,commit给FragmentManager,并存入HashMap以标记为not committed状态,防止重复commit;
- 向Application注册对该Activity的生命周期监听。如果HolderFragment尚未create,Activity就已经销毁,则从HashMap中移除该Activity,防止泄露;
- HolderFragment成功创建后,从HashMap中移除该Activity;
- 使用HolderFragment持有的ViewModelStore对象完成ViewModelProvider的创建;
第一部分的职责是构建 / 查找HolderFragment,对构建过程中的异常做保护,最后返回ViewModelProvider。HolderFragment和ViewModelProvider共同持有的ViewModelStore将成为第二部分的核心;
(第二部分:获取ViewModel)
- 调用ViewModelProvider.get(ViewModel.class);
- 通过ViewModel的规范名(canonical name),从HashMap中查找是否已经存在该ViewModel的实例。有则返回;没有则通过反射构建一个,并存入HashMap;
ViewModelStore.class
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap();
第二部分职责是映射,通过类名从HashMap查找ViewModel实例。整个映射逻辑也可以简化为:通过Activity类名找ViewModel实例;
2) ViewModel的生命周期如何管理?
ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel.
看到官方文档中的这句话,心想ViewModel莫非是通过同为官方架构组件的Lifecycle来管理的生命周期的? 其实并没有那么复杂,ViewModel在第一次调用ViewModelProvider.get(ViewModel.class)创建;而销毁就需要靠HolderFragment自己的onDestroy()回调:
HolderFragment.class
public void onDestroy() {
super.onDestroy(); this.mViewModelStore.clear();
}
ViewModelStore.class
public final void clear() {
Iteratorvar1 = this.mMap.values().iterator();
while(var1.hasNext()){
ViewModel vm = (ViewModel)var1.next();
vm.onCleared();
}
this.mMap.clear();
}
HolderFragment销毁后,调用ViewModelStore.clear(),清理HashMap对ViewModel对象的引用,待系统GC回收ViewModel。这也解释了创建ViewModelProvider时为什么需要HolderFragment配合,HolderFragment掌控了ViewModel的生命周期。
6 Last but not least
简述下官方架构中各组件的主要职责:
- Lifecycle:将Activity /Fragment生命周期回调事件向外抛出;
- LiveData:在Lifecycle范围内监听数据的变化;
- ViewModel:在Lifecycle范围内存储和共享数据;
- Room:简化Db操作;
除了Room,可以感受到官方在尽力把大家从最初的MVC往MVVM引导,更加强大的官方组件将使UI-业务-数据的抽象过程变得更加简单顺滑。
- 使用Zabbix服务端本地邮箱账号发送报警邮件及指定报警邮件操作记录
- fckeditor上传问题的解决
- Flash/Flex学习笔记(46):正向运动学
- 异步Socket处理的一些测试值
- .Net中DES加密的细节问题
- 分布式监控系统Zabbix--完整安装记录 -添加web页面监控
- .Net中使用带UI的OCX的方法
- 2017数据科学领域15大热门GitHub项目
- 简单分页的性能优化
- Flash/Flex学习笔记(42):坐标旋转
- 分布式监控系统Zabbix-完整安装记录 -添加端口监控
- Flash/Flex学习笔记(40):弹性运动续--弹簧
- 表格样式—粗边框细表格线
- 分布式监控系统Zabbix--完整安装记录 -添加apache监控
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python中使用马尔可夫决策过程(MDP)动态编程来解决最短路径强化学习问题
- 《剑指 offer》刷题记录之:树 & 栈和队列
- R语言自定义两种统计量度:平均值和中位数,何时去使用?
- 设计模式之适配器模式
- 自然语言处理中的预训练模型(上)
- numpy数组中冒号和负号的含义
- 基于R语言实现LASSO回归分析
- PHP预防XSS攻击,ajax跨域攻击的方法
- PHP中利用header设置content-type和常见文件类型的content-type
- 《剑指 offer》刷题记录之:递归和循环
- PHP缓存技术介绍
- R语言画ROC曲线总结
- PHP中$_SERVER的详细参数
- R语言使用贝叶斯层次模型进行空间数据分析
- PHP中的小数取整