ViewModel简介
其实Android 平台上之所以会出现诸如MVP、MVVM之类的项目架构,就是因为在传统的开发模式下,Activity 的任务实在是太重 了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,等等。在一个小型项目中 这样写或许没有什么问题,但是如果在大型项目中仍然使用这种写法的话,那么这个项目将会 变得非常臃肿并且难以维护,因为没有任何架构上的划分。
而ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样一定程度上可以减少Activity中的逻辑。
ViewModel可以帮助我们更好地将页面与数据从代码层面分离开来,依赖ViewModel的生命周期特性,我们不需要在关心屏幕旋转导致数据丢失的问题,需要注意的是,如果想在ViewModel中使用Context,建议使用ViewModel的子类AndroidViewModel。
1.视图与数据模型之间的桥梁ViewModel
ViewModel是视图View和数据模型Model之间的沟通桥梁 借助ViewModel, 视图 与 数据模型 实现了解耦,同时还能保证视图 与 数据模型之间 保持通信 这样Activity的代码量减少了,只需要维护视图View相关内容,增加了代码的可维护性。
- 在ViewModel架构中,数据不由View直接进行管理,而是ViewModel进行管理
- 当Activity屏幕旋转,销毁时,只会销毁Activity组件,不会将ViewModel以及数据模型Model销毁
- Activity中的组件获取数据时,不直接从数据模型Model中获取,而是从ViewModel架构组件中获取
- ViewModel作为不同的Activity或Fragment之间沟通的桥梁
1.1 ViewModel与onSaveInstanceState()方法
- ViewModel: ViewModel能支持页面中所有的数据,但是需要注意的是,ViewModel不支持数据的持久化,当页面被彻底销毁时,ViewModel及持有的数据就不存在了
- onSaveInstanceState()方法:只能保存少量的,能支持序列化的数据,但是onSaveInstanceState()方法可以持久化页面的数据。
2.ViewModel的生命周期
如图:
ViewModel的生命周期与Acitivity或Fragment的生命周期相互独立,ViewModel不受Activity组件销毁的影响。如果由于屏幕旋转原因导致的Activity销毁重建,与之绑定的ViewModel会在销毁时解绑,Activity重建时重新绑定。 ViewModel会在应用生命周期内存活,并且可以在Activity或Fragment之间共享数据
ViewModel的生命周期:一个ViewModel实例对象可以与多个Activity或Fragment绑定
- 创建:在Activity的首次启动时 创建ViewModel实例对象,如果Activity多次启动,ViewModel只会创建一次。
- 绑定:Activity与ViewModel关联时,开始绑定ViewModel,Activity组件中绑定ViewModel代码如下:
MainViewModel mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
- 解绑:当Activity或者Fragment被销毁时,与之绑定的ViewModel会与UI组件解绑
- 销毁:ViewModel关联的所有的Activity或Fragment全部销毁,则ViewModel实例对象也会被销毁
3.ViewModel使用
3.1 ViewModel使用注意事项
使用ViewModel时,不要将Context上下文对象传入ViewModel中,否则会导致内存泄漏。
如果要使用Context上下文对象,则ViewModel需要继承AndroidViewModel类,在其构造函数中获取Application对象
AndroidViewModel继承自ViewModel,并接收Application作为Context,因为Application会扩展Context:
public class TimerViewModel extends AndroidViewModel {
public TimerViewModel(@NonNull Application application) {
super(application);
}
}
3.2 使用
3.2.1 创建一个类继承ViewModel
注意:一般情况下,每一个Activity都应该对应一个ViewModel,例如MainActivity,对应的ViewModel应该是MainViewModel.
public class MyViewModel {
public int count;
public MyViewModel(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
3.2.2 在MainActivity中
public class MainActivity extends AppCompatActivity {
private TextView textView;
private MyViewModel myViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取布局组件
textView = findViewById(R.id.textView);
Button btn_add = findViewById(R.id.btn_add);
// 获取 ViewModel
myViewModel new ViewModelProvider(this).get(MyViewModel.class)
btn_add.setOnclickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
myViewModel.setNumber(myViewModel.getNumber() + 1);
textView.setText(String.valueOf(myViewModel.getNumber()));
}
})
// 组件中显示 ViewModel 中的内容
textView.setText(String.valueOf(myViewModel.getNumber()));
}
}
运行效果: 在屏幕旋转后,Activity重建,也不会影响到数据运行,自增操作不会被打断
3.3 向ViewModel传递参数
如果我们确实 需要通过构造函数来传递一些参数,应该怎么办呢?由于所有ViewModel 的实例都是通过 ViewModelP rovider 来获取的,因此我们没有任何地方可以向ViewModel 的构造函数中传递参 数。只需要借助ViewModelProvider.Factory就可以实现了。
现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么 之前的计数就会被清零了。接下来我们就对这一功能进行升级,保证即使在退出程序后又重新 打开的情况下,数据仍然不会丢失。
首先新建一个MyViewModelFactory类,实现ViewModelProvider.Factory接口,
可以看到,MyViewModelFactory中接收了一个参数countReserved,另外ViewModelProvider.Factory接口要求我们必须实现 create() 方法,因此这里在 crate() 方法中我们创建了MyViewModel的实例,并将countReserved参数传了进去。为什么这里就可以创建MainViewModel的实例呢?因为 create() 方法的执行时机和Activity的生命周期无关。
然后我们在界面上添加一个清零按钮,
最后修改MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textview);
Button btn_add = findViewById(R.id.btn_add);
Button btn_clear = findViewById(R.id.btn_clear);
sp = getPreferences(Context.MODE_PRIVATE);
int count_reserved = sp.getInt("count_reserved", 0);
Log.d("aaabbb", "onCreate: "+count_reserved);
//获取ViewModel
myViewModel = new ViewModelProvider(this, new MyViewModelFactory(count_reserved))
.get(MyViewModel.class);
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.setNumber(myViewModel.getNumber() + 1);
textView.setText(String.valueOf(myViewModel.getNumber()));
}
});
btn_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.setNumber(0);
textView.setText(String.valueOf(0));
}
});
textView.setText(String.valueOf(myViewModel.getNumber()));
}
@Override
protected void onPause() {
super.onPause();
Log.d("aaabbb", "onPause(): "+myViewModel.getNumber());
sp.edit().putInt("count_reserved", myViewModel.getNumber()).apply();
}
}
在 oncrate() 方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。
接下来在ViewModelProvider中,额外传入了一个MyViewModelFactory参数,这里将读取到的计数值传给了MyViewModelFactory的构造函数,注意,这一步是非常重要的,只有用这种写法才能将计数值最终传递给MyViewModel的构造函数。
然后我们在clear的按钮中对计数器进行清零,并在 onPause() 方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数器都不会丢失。
现在重新运行程序,点击数次+1 按钮,然后退出程序并重新打开,你会发现,计数器 的值是不会丢失的